UUIDv1 for SQF - official-antistasi-community/A3-Antistasi GitHub Wiki

If viewing in Visual Studio Code, press ctrl+shift+v

Standard RFC 4122 UUID version 1 With Low System Clock Resolution

Source: A Universally Unique IDentifier (UUID) URN Namespace

UUID – Universally Unique Identifier

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            timeLow            |            counter            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            timeMid            |version|       timeHigh        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|       clockSequence       |          node (0β†’1)           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          node (2β†’5)                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Name Definition
UUID Layout timeLow counter "-" timeMid "-" version timeHigh "-" variant clockSequence "-" node
Printed UUID "01234567-89ab-cdef-0123-456789abcdef"
timeLow Least significant bits of UTC time
timeMid Middle significant bits of UTC time
timeHigh Most significant bits of UTC time
version 0001 for time and MAC address based version
var 01 variant for this UUID layout/type
clockSequence Random initial value (0β†’16383) that is increments on change.
node Any connected network card's MAC address (6 octets)

Modified RFC 4122 UUID Version 1 for SQF Implementation

Revision 1 β€” 4 September 2021 β€” Caleb S. Serafin

Table of Contents

Table of Contents
Layout
Field Definitions
 Printed UUID
 time
 counter
 clockSequence
 origin
 node
Cached and Saved Values
 Within Runtime
 In profileNamespace
Algorithms for Producing the Node
 Origin 0
 Origin 1
 Origin 2
Generating a UUID after initialisation
 Example algorithm
UUID Pooling

Layout

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|version|       timeHigh        |            timeMid            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            timeLow            |            counter            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|       clockSequence       | origin|      node (0β†’1)       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          node (2β†’5)                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Name Definition
UUID Layout version timeHigh "-" timeMid "-" timeLow counter "-" variant clockSequence "-" src node
Printed UUID "cdef-89ab-01234567-0123-456789abcdef"
SQF stored UUID 5 elements between 0β†’65535, 2 elements between 0β†’16777215
timeHigh Most significant bits of UTC time
timeMid Middle significant bits of UTC time
timeLow Least significant bits of UTC time
version 0001 for time and MAC address based version
var 01 variant for this UUID layout/type
clockSequence Random initial value (0β†’16383) that is increments on change.
origin Origin of node data and algorithm used.
node 44 bits that attempt to uniquely represent the node.

Node – A machine in a network.

Field Definitions

Printed UUID

To reflect the changes in Endian-ness, the printed version is divided into 4-4-8-4-12 chunks of hexadecimal.

SQF stored UUID

SQF stored UUID optimises the storage of the node by storing it in 2 chunks of 2^24 instead of 3 chunks of 2^16.
The same cannot be applied to other fields, without adding the additional cost of string selects when formatting it with dashes. timeLow and counter cannot be combined as floats do not have 32 bits of accuracy, but only 24.

time

Unlike RFC 4122, the time is ordered in littleEndian. This makes it sortable and easy to select ranges without an intermediary conversion.
Time will not be directly comparable with Unix time as bit packing will take place to convert 7 discrete time values, into 3 discrete time values.
Within SQF it would be an unnecessary expense to convert the 7 discrete time values into 3 continuous segments of a time value. Bit packing is as follows:

  • timeHigh (2^12) = (year mod 341) β€’ 12 + (month - 1)
  • timeMid (2^16) = ((day - 1) β€’ 24 + hour) β€’ 60 + minute
  • timeLow (2^16) = second β€’ 1000 + millisecond

counter

The counter is initialised with a random value.
It is incremented every time a UUID is requested.
It will loop around to 0 at 2^16.

In order for the counter to be exhausted, a UUID will need to be created every 15 nanoseconds. For comparison, on an empty map, merely adding two numbers takes roughly 250 nanoseconds. SQF would require a CPU whose clock frequency is measured in the Terahertz to run a mission and exhaust UUID creation.

clockSequence

This is in addition to the specification provided by RFC 4122.
After clockSequence is loaded, the clockSequence in profileNamespace will be randomised. At the end of runtime, the clockSequence will be saved to profileNamespace. profileNamespace will be flushed after each operation.

If a node crashes, and then reloads, the system will detect this, and reset its clockSequence to a random value. If another instance of the same profileNamespace runs in parallel, it will detect this, and reset its clockSequence to a random value. In the parallel case: the order of saving will not matter.

Crash Example: instance 1 loads, instance 1 crashes, instance 1 resets.
Parallel Example: instance1 loads, instance2 loads reset, instance2 saves, instance3 loads from 2. instance1 saves, instance4 loads from 1, instance5 loads reset,

origin

Specifies the consumed source and algorithm version to produce the node.

  • 0000: 0: steam64
  • 0001: 1: clientOwner ⨂ initialSystemTimeUTC ⨂ serverName
  • 0010: 2: clientOwner ⨂ serverName
  • 0011.*: future definitions

⨂ – In this document it will represent arbitrary combining two elements.

Arbitrary – Undetermined; No assigned specific value or method.

node

The node can be treated as 6 octets. The most significant octet's bits are replaced by the origin code.

MAC address is not available. It is replaced by mostly stable sources:

  1. Clients will use origin 0. This is regenerated if the steam64 changes (swapping profile.vars)
  2. Dedicated servers will use origin 1. This is regenerated if the serverName changes.
  3. Headless clients will use origin 1. This is regenerated if the serverName changes.

Problems and Solutions:

  1. Steam64s are not available before the player object has been initialised on clients. This is mitigated by caching the steam64 in the profileNamespace.
  2. If the steam64 is not available in the profileNamespace when a UUID is requested before the player object exists, the client will use origin 2. The initialSystemTimeUTC is not used as this node will only need to be valid for an extremely short time period, therefore the time segments will be sufficiently close.
  3. A clientOwner collision in origin 1 would be very likely due to only 16 IDs. Therefore, headless clients are manually assigned IDs by the server to ensure there is no collision. This means that when it's not cached and before it's assigned, HCs will use origin 2.

Cached and Saved Values

Within Runtime

  • counter (scalar)
  • clockSequence (scalar)
  • origin ⨂ node (array)
  • variant ⨂ clockSequence ⨂ origin ⨂ node (array)

In profileNamespace

  • systemTimeUTC (array)
  • clockSequence (scalar)
  • origin (scalar)
  • origin ⨂ node (array)
  • steam64 (string)
  • serverName (string)
  • headlessClientID (scalar)

Algorithms for Producing the Node

Origin 0

  1. The client's steam64 will be fetched.
  2. A least significant chunk (LSC) of 7 digits will be extracted from digits 0→6 (right→left)
  3. A most significant chunk (MSC) of 7 digits will be extracted from digits 7→13 (right→left)
  4. Each will be parsed into a scalar.
  5. MSC will be limited between 0β†’7825791 ((2^20 + (2^24 + 10^7))).
  6. The MSC will have 6777216 ((2^44 - 10^7)) subtracted from it. If it is smaller than this amount, then only its current value will be taken.
  7. The amount taken from MSC will be added to LSC.
  8. The return value will be [MSC, LSC]. (Origin 0's code is not added as it is 0)

Limiting a scalar to a range value will be done with the x modulo y operation rather than 0 max x min y.

Origin 1

  1. days and hours will be extracted from systemTimeUTC at generation.
  2. days will be decremented and limited between 0β†’9.
  3. timePart will be equal to (days * 24 + hours) (~2^8). This is based on the idea that most servers will update their mission around a week (especially servers that they copy another server's name).
  4. serverName will be hashed using hashValue. This results a 11 character string of base64. NOTE: the last character will have a lower range than the rest, so only 10 full characters.
  5. Each of first 8 characters will be converted from base64, this will be an array of 8 items 0β†’63.
  6. 4 buckets will be created (0β†’3).
  7. The 8 values will be combined into 4 buckets. Each pair of values will be combined by item1<<6 + item2.
  8. Each bucket will be limited between 0β†’255 (2^8).
  9. headlessClientID will be limited between 1β†’15 (2^4). This is assigned manually from the server. Server uses 0. Theses are statically mapped by HC name.
  10. origin code is 1.
  11. The return value will be [origin<<20 + headlessClientID<<16 + time<<8 + bucket#0, bucket#1<<16 + bucket#2<<8 + bucket#3].

<<, >> – Bit shifts are implemented by multiplying by a positive or negative power of 2.

# – Select element from a zero-based list.

Origin 2

  1. serverName will be hashed using hashValue. This results a 11 character string of base64. NOTE: the last character will have a lower range than the rest, so only 10 full characters.
  2. Each of first 8 characters will be converted from base64, this will be an array of 8 items 0β†’63.
  3. 4 buckets will be created (0β†’3).
  4. The 8 values will be combined into 4 buckets. Each pair of values will be combined by item1<<6 + item2.
  5. Each bucket will be limited between 0β†’255 (2^8).
  6. clientOwner will be limited between 0β†’4095 (2^12).
  7. origin code is 2.
  8. The return value will be [origin<<20 + clientOwner<<8 + bucket#0, bucket#1<<16 + bucket#2<<8 + bucket#3].

Generating a UUID after initialisation

Example algorithm

  1. Get systemTimeUTC as year, month, day, hour, minute, second, millisecond.
  2. timeHigh (2^12) = (year mod 341) * 12 + (month - 1)
  3. timeMid (2^16) = ((day - 1) * 24 + hour) * 60 + minute
  4. timeLow (2^16) = second * 1000 + millisecond
  5. timeHigh = timeHigh + 4096 (Add version 1 (1 * 2^12))
  6. START UNSCHEDULED
  7. count = counter (get from global)
  8. Increment global counter
  9. END UNSCHEDULED
  10. UUID = [timeHigh, timeMid, timeLow, count]
  11. UUID append cachedClockSequenceAndNode
  12. return UUID

The caller will also be able to request for a UUID directly in string form. In this case, the saved string of cachedClockSequenceAndNode can be used, saving the time of formatting 16 hexadecimal digits.

UUID Pooling

UUID get methods can utilise pooling to reduce the overhead of fetching single or bulk UUIDs. The pool can be increased periodically by a background process. If the pool is empty, fresh UUIDs be generated. Callers can also request a fresh UUID if so desired (For time recording purposes). Requesting a fresh UUID will not require the pool to be dropped.

Pulling items from the pool does not require an unscheduled scope, because SQF's deleteAt both gets and removes an item from an array. Therefore the process of popping a UUID is atomic. The UUID pooling frequency, increaseAmount, and maxSize can be manually tweaked based on mission UUID usage.

Atomic – An operation that cannot be broken up into smaller parts. Cannot be suspended mid-execution.



[Back to Top ↑]

⚠️ **GitHub.com Fallback** ⚠️