mirror of
https://github.com/penpot/penpot.git
synced 2025-02-02 04:19:08 -05:00
✨ Improve UUIDv8 implementation
This commit is contained in:
parent
9c68432936
commit
d082ff0a2b
3 changed files with 138 additions and 96 deletions
|
@ -10,12 +10,14 @@
|
||||||
|
|
||||||
It has the following characteristics:
|
It has the following characteristics:
|
||||||
- time ordered
|
- time ordered
|
||||||
- 48bits timestamp
|
- 48bits timestamp (milliseconds precision, with custom epoch: 2022-01-01T00:00:00)
|
||||||
- custom epoch: milliseconds since 2022-01-01T00:00:00
|
- 14bits random clockseq (monotonically increasing on timestamp conflict)
|
||||||
- 14bits monotonic clockseq (allows generate 16k uuids/ms)
|
- spin locks (blocks) if more than 16384 ids/ms is generated in a single host
|
||||||
- mostly static random 60 bits (initialized at class load or clock regression)
|
- 56bits of randomnes generated statically on load (resets on clock regression)
|
||||||
|
- 4 bits of user defined tag (defaults to 1 on jvm and 0 on js)
|
||||||
|
|
||||||
This is results in a constantly increasing, sortable, very fast uuid impl.
|
This results in a constantly increasing, sortable, very fast
|
||||||
|
and easy to visually read uuid implementation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package app.common;
|
package app.common;
|
||||||
|
@ -23,59 +25,98 @@ package app.common;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
public class UUIDv8 {
|
public class UUIDv8 {
|
||||||
public static final long timeRef = 1640991600L * 1000L; // ms since 2022-01-01T00:00:00
|
private static final long timeRef = 1640995200000L; // ms since 2022-01-01T00:00:00
|
||||||
public static final long clockSeqMax = 16384L; // 14 bits space
|
private static final Clock clock = Clock.systemUTC();
|
||||||
public static final Clock clock = Clock.systemUTC();
|
private static final Lock lock = new ReentrantLock();
|
||||||
|
private static final long baseMsb = 0x0000_0000_0000_8000L; // Version 8
|
||||||
|
private static final long baseLsb = 0x8000_0000_0000_0000L; // Variant 2
|
||||||
|
private static final long maxCs = 0x0000_0000_0000_3fffL;
|
||||||
|
|
||||||
public static long baseMsb;
|
private static final SecureRandom srandom = new java.security.SecureRandom();
|
||||||
public static long baseLsb;
|
|
||||||
public static long clockSeq = 0L;
|
|
||||||
public static long lastTs = 0L;
|
|
||||||
|
|
||||||
public static SecureRandom srandom = new java.security.SecureRandom();
|
private static long countCs = 0L;
|
||||||
|
private static long lastCs = 0L;
|
||||||
public static synchronized void initializeSeed() {
|
private static long lastTs = 0L;
|
||||||
baseMsb = 0x0000_0000_0000_8000L; // Version 8
|
private static long lastRd = 0L;
|
||||||
baseLsb = srandom.nextLong() & 0x0fff_ffff_ffff_ffffL | 0x8000_0000_0000_0000L; // Variant 2
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
initializeSeed();
|
lastRd = (srandom.nextLong() & 0xffff_ffff_ffff_f1ffL);
|
||||||
|
lastCs = (srandom.nextLong() & maxCs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized UUID create(final long ts, final long clockSeq) {
|
public static UUID create(final long ts, final long lastRd, final long lastCs) {
|
||||||
long msb = (baseMsb
|
long msb = (baseMsb
|
||||||
| ((ts << 16) & 0xffff_ffff_ffff_0000L)
|
| (lastRd & 0xffff_ffff_ffff_0fffL));
|
||||||
| ((clockSeq >>> 2) & 0x0000_0000_0000_0fffL));
|
|
||||||
long lsb = baseLsb | ((clockSeq << 60) & 0x3000_0000_0000_0000L);
|
long lsb = (baseLsb
|
||||||
|
| ((ts << 14) & 0x3fff_ffff_ffff_c000L)
|
||||||
|
| lastCs);
|
||||||
|
|
||||||
return new UUID(msb, lsb);
|
return new UUID(msb, lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized UUID create() {
|
public static void setTag(final long tag) {
|
||||||
while (true) {
|
lock.lock();
|
||||||
long ts = clock.millis() - timeRef;
|
try {
|
||||||
|
if (tag > 0x0000_0000_0000_000fL) {
|
||||||
// Protect from clock regression
|
throw new IllegalArgumentException("tag value should fit in 4bits");
|
||||||
if ((ts - lastTs) < 0) {
|
|
||||||
initializeSeed();
|
|
||||||
clockSeq = 0;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastTs == ts) {
|
lastRd = (lastRd
|
||||||
if (clockSeq < clockSeqMax) {
|
& 0xffff_ffff_ffff_f0ffL
|
||||||
clockSeq++;
|
| ((tag << 8) & 0x0000_0000_0000_0f00L));
|
||||||
} else {
|
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instant getTimestamp(final UUID uuid) {
|
||||||
|
final long lsb = uuid.getLeastSignificantBits();
|
||||||
|
return Instant.EPOCH.plusMillis(timeRef).plusMillis((lsb >>> 14) & 0x0000_ffff_ffff_ffffL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UUID create() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
long ts = (clock.millis() - timeRef); // in millis
|
||||||
|
|
||||||
|
// If clock regression happens, regenerate lastRd
|
||||||
|
if ((ts - lastTs) < 0) {
|
||||||
|
// Clear and replace the 56 bits of randomness (60bits - 4 bits tag)
|
||||||
|
lastRd = (lastRd
|
||||||
|
& 0x0000_0000_0000_0f00L
|
||||||
|
| (srandom.nextLong() & 0xffff_ffff_ffff_f0ffL));
|
||||||
|
|
||||||
|
countCs = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
lastTs = ts;
|
|
||||||
clockSeq = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return create(ts, clockSeq);
|
// If last timestamp is the same as the current one we proceed
|
||||||
|
// to increment the counters.
|
||||||
|
if (lastTs == ts) {
|
||||||
|
if (countCs < maxCs) {
|
||||||
|
lastCs = (lastCs + 1L) & maxCs;
|
||||||
|
countCs++;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastTs = ts;
|
||||||
|
lastCs = srandom.nextLong() & maxCs;
|
||||||
|
countCs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return create(ts, lastRd, lastCs);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,30 +43,6 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2010-2016 Robert Kieffer and other contributors
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const hexMap = [];
|
const hexMap = [];
|
||||||
for (let i = 0; i < 256; i++) {
|
for (let i = 0; i < 256; i++) {
|
||||||
hexMap[i] = (i + 0x100).toString(16).substr(1);
|
hexMap[i] = (i + 0x100).toString(16).substr(1);
|
||||||
|
@ -105,70 +81,95 @@ goog.scope(function() {
|
||||||
|
|
||||||
self.v8 = (function () {
|
self.v8 = (function () {
|
||||||
const buff = new ArrayBuffer(16);
|
const buff = new ArrayBuffer(16);
|
||||||
const buff8 = new Uint8Array(buff);
|
const int8 = new Uint8Array(buff);
|
||||||
const view = new DataView(buff);
|
const view = new DataView(buff);
|
||||||
|
|
||||||
const timeRef = 1640991600 * 1000; // ms since 2022-01-01T00:00:00
|
const tmpBuff = new ArrayBuffer(8);
|
||||||
const maxClockSeq = 16384n; // 14 bits space
|
const tmpView = new DataView(tmpBuff);
|
||||||
|
const tmpInt8 = new Uint8Array(tmpBuff);
|
||||||
|
|
||||||
let clockSeq = 0n;
|
const timeRef = 1640995200000; // ms since 2022-01-01T00:00:00
|
||||||
let lastTs = 0n;
|
const maxCs = 0x0000_0000_0000_3fffn; // 14 bits space
|
||||||
let baseMsb;
|
|
||||||
let baseLsb;
|
|
||||||
|
|
||||||
function initializeSeed() {
|
let countCs = 0n;
|
||||||
fill(buff8);
|
let lastRd = 0n;
|
||||||
baseMsb = 0x0000_0000_0000_8000n; // Version 8;
|
let lastCs = 0n;
|
||||||
baseLsb = view.getBigUint64(8, false) & 0x0fff_ffff_ffff_ffffn | 0x8000_0000_0000_0000n; // Variant 2;
|
let lastTs = 0n;
|
||||||
}
|
let baseMsb = 0x0000_0000_0000_8000n;
|
||||||
|
let baseLsb = 0x8000_0000_0000_0000n;
|
||||||
|
|
||||||
function currentTimestamp() {
|
const currentTimestamp = () => {
|
||||||
return BigInt.asUintN(64, "" + (Date.now() - timeRef));
|
return BigInt.asUintN(64, "" + (Date.now() - timeRef));
|
||||||
}
|
};
|
||||||
|
|
||||||
initializeSeed();
|
const nextLong = () => {
|
||||||
|
fill(tmpInt8);
|
||||||
|
return tmpView.getBigUint64(0, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
lastRd = nextLong() & 0xffff_ffff_ffff_f0ffn;
|
||||||
|
lastCs = nextLong() & maxCs;
|
||||||
|
|
||||||
|
const create = function create(ts, lastRd, lastCs) {
|
||||||
|
const msb = (baseMsb
|
||||||
|
| (lastRd & 0xffff_ffff_ffff_0fffn));
|
||||||
|
|
||||||
|
const lsb = (baseLsb
|
||||||
|
| ((ts << 14n) & 0x3fff_ffff_ffff_c000n)
|
||||||
|
| lastCs);
|
||||||
|
|
||||||
const create = function create(ts, clockSeq) {
|
|
||||||
let msb = (baseMsb
|
|
||||||
| ((ts << 16n) & 0xffff_ffff_ffff_0000n)
|
|
||||||
| ((clockSeq >> 2n) & 0x0000_0000_0000_0fffn));
|
|
||||||
let lsb = baseLsb | ((clockSeq << 60n) & 0x3000_0000_0000_0000n);
|
|
||||||
view.setBigUint64(0, msb, false);
|
view.setBigUint64(0, msb, false);
|
||||||
view.setBigUint64(8, lsb, false);
|
view.setBigUint64(8, lsb, false);
|
||||||
return core.uuid(toHexString(buff8));
|
return core.uuid(toHexString(int8));
|
||||||
}
|
};
|
||||||
|
|
||||||
const factory = function v8() {
|
const factory = function v8() {
|
||||||
while (true) {
|
while (true) {
|
||||||
let ts = currentTimestamp();
|
let ts = currentTimestamp();
|
||||||
|
|
||||||
// Protect from clock regression
|
// Protect from clock regression
|
||||||
if ((ts-lastTs) < 0) {
|
if ((ts - lastTs) < 0) {
|
||||||
initializeSeed();
|
lastRd = (lastRd
|
||||||
clockSeq = 0;
|
& 0x0000_0000_0000_0f00n
|
||||||
|
| (nextLong() & 0xffff_ffff_ffff_f0ffn));
|
||||||
|
countCs = 0n;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastTs === ts) {
|
if (lastTs === ts) {
|
||||||
if (clockSeq < maxClockSeq) {
|
if (countCs < maxCs) {
|
||||||
clockSeq++;
|
lastCs = (lastCs + 1n) & maxCs;
|
||||||
|
countCs++;
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clockSeq = 0n;
|
|
||||||
lastTs = ts;
|
lastTs = ts;
|
||||||
|
lastCs = nextLong() & maxCs;
|
||||||
|
countCs = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return create(ts, clockSeq);
|
return create(ts, lastRd, lastCs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
factory.create = create
|
const setTag = (tag) => {
|
||||||
factory.initialize = initializeSeed;
|
tag = BigInt.asUintN(64, "" + tag);
|
||||||
|
if (tag > 0x0000_0000_0000_000fn) {
|
||||||
|
throw new Error("illegal arguments: tag value should fit in 4bits");
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRd = (lastRd
|
||||||
|
& 0xffff_ffff_ffff_f0ffn
|
||||||
|
| ((tag << 8) & 0x0000_0000_0000_0f00n));
|
||||||
|
};
|
||||||
|
|
||||||
|
factory.create = create;
|
||||||
|
factory.setTag = setTag;
|
||||||
return factory;
|
return factory;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
self.custom = function formatAsUUID(mostSigBits, leastSigBits) {
|
self.custom = function formatAsUUID(mostSigBits, leastSigBits) {
|
||||||
const most = mostSigBits.toString("16").padStart(16, "0");
|
const most = mostSigBits.toString("16").padStart(16, "0");
|
||||||
const least = leastSigBits.toString("16").padStart(16, "0");
|
const least = leastSigBits.toString("16").padStart(16, "0");
|
||||||
|
|
Binary file not shown.
Loading…
Add table
Reference in a new issue