mirror of
https://github.com/penpot/penpot.git
synced 2025-02-08 16:18:11 -05:00
Merge pull request #5325 from penpot/niwinz-uuid-tweaks
✨ Improve Uint32 array generation from uuid
This commit is contained in:
commit
4e28f1b1f7
6 changed files with 157 additions and 73 deletions
|
@ -115,6 +115,7 @@
|
||||||
{:id "n"
|
{:id "n"
|
||||||
:rfn (fn [value]
|
:rfn (fn [value]
|
||||||
(js/parseInt value 10))})
|
(js/parseInt value 10))})
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
{:id "u"
|
{:id "u"
|
||||||
:rfn parse-uuid})
|
:rfn parse-uuid})
|
||||||
|
|
|
@ -17,32 +17,32 @@
|
||||||
java.util.UUID
|
java.util.UUID
|
||||||
java.nio.ByteBuffer)))
|
java.nio.ByteBuffer)))
|
||||||
|
|
||||||
(def zero #uuid "00000000-0000-0000-0000-000000000000")
|
(defn uuid
|
||||||
|
"Parse string uuid representation into proper UUID instance."
|
||||||
(defn zero?
|
[s]
|
||||||
[v]
|
#?(:clj (UUID/fromString s)
|
||||||
(= zero v))
|
:cljs (c/uuid s)))
|
||||||
|
|
||||||
(defn next
|
(defn next
|
||||||
[]
|
[]
|
||||||
#?(:clj (UUIDv8/create)
|
#?(:clj (UUIDv8/create)
|
||||||
:cljs (impl/v8)))
|
:cljs (uuid (impl/v8))))
|
||||||
|
|
||||||
(defn random
|
(defn random
|
||||||
"Alias for clj-uuid/v4."
|
"Alias for clj-uuid/v4."
|
||||||
[]
|
[]
|
||||||
#?(:clj (UUID/randomUUID)
|
#?(:clj (UUID/randomUUID)
|
||||||
:cljs (impl/v4)))
|
:cljs (uuid (impl/v4))))
|
||||||
|
|
||||||
(defn uuid
|
|
||||||
"Parse string uuid representation into proper UUID instance."
|
|
||||||
[s]
|
|
||||||
#?(:clj (UUID/fromString s)
|
|
||||||
:cljs (c/parse-uuid s)))
|
|
||||||
|
|
||||||
(defn custom
|
(defn custom
|
||||||
([a] #?(:clj (UUID. 0 a) :cljs (c/parse-uuid (impl/custom 0 a))))
|
([a] #?(:clj (UUID. 0 a) :cljs (uuid (impl/custom 0 a))))
|
||||||
([b a] #?(:clj (UUID. b a) :cljs (c/parse-uuid (impl/custom b a)))))
|
([b a] #?(:clj (UUID. b a) :cljs (uuid (impl/custom b a)))))
|
||||||
|
|
||||||
|
(def zero (uuid "00000000-0000-0000-0000-000000000000"))
|
||||||
|
|
||||||
|
(defn zero?
|
||||||
|
[v]
|
||||||
|
(= zero v))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(defn get-word-high
|
(defn get-word-high
|
||||||
|
@ -54,32 +54,41 @@
|
||||||
[id]
|
[id]
|
||||||
(.getLeastSignificantBits ^UUID id)))
|
(.getLeastSignificantBits ^UUID id)))
|
||||||
|
|
||||||
#?(:clj
|
(defn get-bytes
|
||||||
(defn get-bytes
|
|
||||||
[^UUID o]
|
[^UUID o]
|
||||||
|
#?(:clj
|
||||||
(let [buf (ByteBuffer/allocate 16)]
|
(let [buf (ByteBuffer/allocate 16)]
|
||||||
(.putLong buf (.getMostSignificantBits o))
|
(.putLong buf (.getMostSignificantBits o))
|
||||||
(.putLong buf (.getLeastSignificantBits o))
|
(.putLong buf (.getLeastSignificantBits o))
|
||||||
(.array buf))))
|
(.array buf))
|
||||||
|
:cljs
|
||||||
|
(impl/getBytes (.-uuid o))))
|
||||||
|
|
||||||
#?(:clj
|
(defn from-bytes
|
||||||
(defn from-bytes
|
|
||||||
[^bytes o]
|
[^bytes o]
|
||||||
|
#?(:clj
|
||||||
(let [buf (ByteBuffer/wrap o)]
|
(let [buf (ByteBuffer/wrap o)]
|
||||||
(UUID. ^long (.getLong buf)
|
(UUID. ^long (.getLong buf)
|
||||||
^long (.getLong buf)))))
|
^long (.getLong buf)))
|
||||||
|
:cljs
|
||||||
|
(uuid (impl/fromBytes o))))
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn uuid->short-id
|
(defn uuid->short-id
|
||||||
"Return a shorter string of a safe subset of bytes of an uuid encoded
|
"Return a shorter string of a safe subset of bytes of an uuid encoded
|
||||||
with base62. It is only safe to use with uuid v4 and penpot custom v8"
|
with base62. It is only safe to use with uuid v4 and penpot custom v8"
|
||||||
[id]
|
[id]
|
||||||
(impl/short-v8 (dm/str id))))
|
(impl/shortV8 (dm/str id))))
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn uuid->u32
|
(defn get-u32
|
||||||
[id]
|
[this]
|
||||||
(impl/get-u32 (dm/str id))))
|
(let [buffer (unchecked-get this "__u32_buffer")]
|
||||||
|
(if (nil? buffer)
|
||||||
|
(let [buffer (impl/getUnsignedInt32Array (.-uuid ^UUID this))]
|
||||||
|
(unchecked-set this "__u32_buffer" buffer)
|
||||||
|
buffer)
|
||||||
|
buffer))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(defn hash-int
|
(defn hash-int
|
||||||
|
@ -88,4 +97,3 @@
|
||||||
b (.getLeastSignificantBits ^UUID id)]
|
b (.getLeastSignificantBits ^UUID id)]
|
||||||
(+ (clojure.lang.Murmur3/hashLong a)
|
(+ (clojure.lang.Murmur3/hashLong a)
|
||||||
(clojure.lang.Murmur3/hashLong b)))))
|
(clojure.lang.Murmur3/hashLong b)))))
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,10 @@
|
||||||
*/
|
*/
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
goog.require("cljs.core");
|
|
||||||
goog.require("app.common.encoding_impl");
|
goog.require("app.common.encoding_impl");
|
||||||
goog.provide("app.common.uuid_impl");
|
goog.provide("app.common.uuid_impl");
|
||||||
|
|
||||||
goog.scope(function() {
|
goog.scope(function() {
|
||||||
const core = cljs.core;
|
|
||||||
const global = goog.global;
|
const global = goog.global;
|
||||||
const encoding = app.common.encoding_impl;
|
const encoding = app.common.encoding_impl;
|
||||||
const self = app.common.uuid_impl;
|
const self = app.common.uuid_impl;
|
||||||
|
@ -129,7 +127,7 @@ goog.scope(function() {
|
||||||
fill(arr);
|
fill(arr);
|
||||||
arr[6] = (arr[6] & 0x0f) | 0x40;
|
arr[6] = (arr[6] & 0x0f) | 0x40;
|
||||||
arr[8] = (arr[8] & 0x3f) | 0x80;
|
arr[8] = (arr[8] & 0x3f) | 0x80;
|
||||||
return core.uuid(encoding.bufferToHex(arr, true));
|
return encoding.bufferToHex(arr, true);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -161,7 +159,7 @@ goog.scope(function() {
|
||||||
setBigUint64(view, 0, msb, false);
|
setBigUint64(view, 0, msb, false);
|
||||||
setBigUint64(view, 8, lsb, false);
|
setBigUint64(view, 8, lsb, false);
|
||||||
|
|
||||||
return core.uuid(encoding.bufferToHex(int8, true));
|
return encoding.bufferToHex(int8, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const factory = function v8() {
|
const factory = function v8() {
|
||||||
|
@ -194,6 +192,11 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fromArray = (u8data) => {
|
||||||
|
int8.set(u8data);
|
||||||
|
return encoding.bufferToHex(int8, true);
|
||||||
|
}
|
||||||
|
|
||||||
const setTag = (tag) => {
|
const setTag = (tag) => {
|
||||||
tag = BigInt.asUintN(64, "" + tag);
|
tag = BigInt.asUintN(64, "" + tag);
|
||||||
if (tag > 0x0000_0000_0000_000fn) {
|
if (tag > 0x0000_0000_0000_000fn) {
|
||||||
|
@ -207,10 +210,11 @@ goog.scope(function() {
|
||||||
|
|
||||||
factory.create = create;
|
factory.create = create;
|
||||||
factory.setTag = setTag;
|
factory.setTag = setTag;
|
||||||
|
factory.fromArray = fromArray;
|
||||||
return factory;
|
return factory;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
self.short_v8 = function(uuid) {
|
self.shortV8 = function(uuid) {
|
||||||
const buff = encoding.hexToBuffer(uuid);
|
const buff = encoding.hexToBuffer(uuid);
|
||||||
const short = new Uint8Array(buff, 4);
|
const short = new Uint8Array(buff, 4);
|
||||||
return encoding.bufferToBase62(short);
|
return encoding.bufferToBase62(short);
|
||||||
|
@ -222,39 +226,61 @@ goog.scope(function() {
|
||||||
return `${most.substring(0, 8)}-${most.substring(8, 12)}-${most.substring(12)}-${least.substring(0, 4)}-${least.substring(4)}`;
|
return `${most.substring(0, 8)}-${most.substring(8, 12)}-${most.substring(12)}-${least.substring(0, 4)}-${least.substring(4)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.get_u32 = (function() {
|
self.fromBytes = function(data) {
|
||||||
const UUID_BYTE_SIZE = 16;
|
if (data instanceof Uint8Array) {
|
||||||
const ab = new ArrayBuffer(UUID_BYTE_SIZE);
|
return self.v8.fromArray(data);
|
||||||
const u32buffer = new Uint32Array(ab);
|
} else if (data instanceof Int8Array) {
|
||||||
const HYPHEN = '-'.charCodeAt(0);
|
data = Uint8Array.from(data);
|
||||||
const A = 'a'.charCodeAt(0);
|
return self.v8.fromArray(data);
|
||||||
const A_SUB = A - 10;
|
|
||||||
const ZERO = '0'.charCodeAt(0);
|
|
||||||
const MAX_DIGIT = 8;
|
|
||||||
const HALF_BITS = 4;
|
|
||||||
return function(uuid) {
|
|
||||||
let digit = 0;
|
|
||||||
let numDigit = 0;
|
|
||||||
let u32index = 0;
|
|
||||||
let u32 = 0;
|
|
||||||
for (let i = 0; i < uuid.length; i++) {
|
|
||||||
const charCode = uuid.charCodeAt(i);
|
|
||||||
if (charCode === HYPHEN) continue;
|
|
||||||
if (charCode >= A) {
|
|
||||||
digit = charCode - A_SUB;
|
|
||||||
} else {
|
} else {
|
||||||
digit = charCode - ZERO;
|
let buffer = data?.buffer;
|
||||||
}
|
if (buffer instanceof ArrayBuffer) {
|
||||||
numDigit++;
|
data = new Uint8Array(buffer);
|
||||||
const bitPos = (MAX_DIGIT - numDigit) * HALF_BITS;
|
return self.v8.fromArray(data);
|
||||||
u32 |= (digit << bitPos);
|
} else {
|
||||||
if (numDigit === MAX_DIGIT) {
|
throw new Error("invalid array type received");
|
||||||
u32buffer[u32index++] = u32;
|
|
||||||
u32 = 0;
|
|
||||||
numDigit = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return u32buffer;
|
};
|
||||||
|
|
||||||
|
// Code based from uuidjs/parse.ts
|
||||||
|
self.getBytes = function parse(uuid) {
|
||||||
|
const buffer = new ArrayBuffer(16);
|
||||||
|
const view = new Int8Array(buffer);
|
||||||
|
let rest;
|
||||||
|
|
||||||
|
// Parse ########-....-....-....-............
|
||||||
|
view[0] = (rest = parseInt(uuid.slice(0, 8), 16)) >>> 24;
|
||||||
|
view[1] = (rest >>> 16) & 0xff;
|
||||||
|
view[2] = (rest >>> 8) & 0xff;
|
||||||
|
view[3] = rest & 0xff;
|
||||||
|
|
||||||
|
// Parse ........-####-....-....-............
|
||||||
|
view[4] = (rest = parseInt(uuid.slice(9, 13), 16)) >>> 8;
|
||||||
|
view[5] = rest & 0xff;
|
||||||
|
|
||||||
|
// Parse ........-....-####-....-............
|
||||||
|
view[6] = (rest = parseInt(uuid.slice(14, 18), 16)) >>> 8;
|
||||||
|
view[7] = rest & 0xff;
|
||||||
|
|
||||||
|
// Parse ........-....-....-####-............
|
||||||
|
view[8] = (rest = parseInt(uuid.slice(19, 23), 16)) >>> 8;
|
||||||
|
view[9] = rest & 0xff,
|
||||||
|
|
||||||
|
// Parse ........-....-....-....-############
|
||||||
|
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
|
||||||
|
view[10] = ((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff;
|
||||||
|
view[11] = (rest / 0x100000000) & 0xff;
|
||||||
|
view[12] = (rest >>> 24) & 0xff;
|
||||||
|
view[13] = (rest >>> 16) & 0xff;
|
||||||
|
view[14] = (rest >>> 8) & 0xff;
|
||||||
|
view[15] = rest & 0xff;
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.getUnsignedInt32Array = function (uuid) {
|
||||||
|
const bytes = self.getBytes(uuid);
|
||||||
|
return new Uint32Array(bytes.buffer);
|
||||||
}
|
}
|
||||||
})();
|
|
||||||
});
|
});
|
||||||
|
|
47
common/test/common_tests/uuid_test.cljc
Normal file
47
common/test/common_tests/uuid_test.cljc
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns common-tests.uuid-test
|
||||||
|
(:require
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[clojure.test :as t]))
|
||||||
|
|
||||||
|
(defn create-array
|
||||||
|
[data]
|
||||||
|
#?(:clj (byte-array data)
|
||||||
|
:cljs (.from js/Int8Array (into-array data))))
|
||||||
|
|
||||||
|
(t/deftest bytes-roundtrip
|
||||||
|
(let [uuid (uuid/uuid "0227df82-63d7-8016-8005-48d9c0f33011")
|
||||||
|
result-bytes (uuid/get-bytes uuid)
|
||||||
|
expected-bytes [2 39 -33 -126 99 -41 -128 22 -128 5 72 -39 -64 -13 48 17]]
|
||||||
|
(t/testing "get-bytes"
|
||||||
|
(let [data (uuid/get-bytes uuid)]
|
||||||
|
(t/is (= (nth expected-bytes 0) (aget data 0)))
|
||||||
|
(t/is (= (nth expected-bytes 1) (aget data 1)))
|
||||||
|
(t/is (= (nth expected-bytes 2) (aget data 2)))
|
||||||
|
(t/is (= (nth expected-bytes 3) (aget data 3)))
|
||||||
|
(t/is (= (nth expected-bytes 4) (aget data 4)))
|
||||||
|
(t/is (= (nth expected-bytes 5) (aget data 5)))
|
||||||
|
(t/is (= (nth expected-bytes 6) (aget data 6)))
|
||||||
|
(t/is (= (nth expected-bytes 7) (aget data 7)))
|
||||||
|
(t/is (= (nth expected-bytes 8) (aget data 8)))
|
||||||
|
(t/is (= (nth expected-bytes 9) (aget data 9)))
|
||||||
|
(t/is (= (nth expected-bytes 10) (aget data 10)))
|
||||||
|
(t/is (= (nth expected-bytes 11) (aget data 11)))
|
||||||
|
(t/is (= (nth expected-bytes 12) (aget data 12)))
|
||||||
|
(t/is (= (nth expected-bytes 13) (aget data 13)))
|
||||||
|
(t/is (= (nth expected-bytes 14) (aget data 14)))
|
||||||
|
(t/is (= (nth expected-bytes 15) (aget data 15)))))
|
||||||
|
|
||||||
|
(t/testing "from-bytes"
|
||||||
|
(let [data (create-array expected-bytes)
|
||||||
|
result (uuid/from-bytes data)]
|
||||||
|
(t/is (= result uuid))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,13 @@
|
||||||
|
|
||||||
(defn create-shape
|
(defn create-shape
|
||||||
[id]
|
[id]
|
||||||
(let [buffer (uuid/uuid->u32 id)
|
(let [buffer (uuid/get-u32 id)
|
||||||
create-shape (unchecked-get internal-module "_create_shape")]
|
create-shape (unchecked-get internal-module "_create_shape")]
|
||||||
(^function create-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
|
(^function create-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
|
||||||
|
|
||||||
(defn use-shape
|
(defn use-shape
|
||||||
[id]
|
[id]
|
||||||
(let [buffer (uuid/uuid->u32 id)
|
(let [buffer (uuid/get-u32 id)
|
||||||
use-shape (unchecked-get internal-module "_use_shape")]
|
use-shape (unchecked-get internal-module "_use_shape")]
|
||||||
(^function use-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
|
(^function use-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
add-shape-child (unchecked-get internal-module "_add_shape_child")]
|
add-shape-child (unchecked-get internal-module "_add_shape_child")]
|
||||||
(^function clear-shape-children)
|
(^function clear-shape-children)
|
||||||
(doseq [id shape_ids]
|
(doseq [id shape_ids]
|
||||||
(let [buffer (uuid/uuid->u32 id)]
|
(let [buffer (uuid/get-u32 id)]
|
||||||
(^function add-shape-child (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))))
|
(^function add-shape-child (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))))
|
||||||
|
|
||||||
(defn set-shape-fills
|
(defn set-shape-fills
|
||||||
|
|
|
@ -61,14 +61,16 @@
|
||||||
(cancel-idle-callback sem))))))
|
(cancel-idle-callback sem))))))
|
||||||
|
|
||||||
(def ^:private request-animation-frame
|
(def ^:private request-animation-frame
|
||||||
(or (and (exists? js/window) (.-requestAnimationFrame js/window))
|
(if (and (exists? js/globalThis)
|
||||||
|
(exists? (.-requestAnimationFrame js/globalThis)))
|
||||||
|
#(.requestAnimationFrame js/globalThis %)
|
||||||
#(js/setTimeout % 16)))
|
#(js/setTimeout % 16)))
|
||||||
|
|
||||||
(defn raf
|
(defn raf
|
||||||
[f]
|
[f]
|
||||||
(request-animation-frame f))
|
(^function request-animation-frame f))
|
||||||
|
|
||||||
(defn idle-then-raf
|
(defn idle-then-raf
|
||||||
[f]
|
[f]
|
||||||
(schedule-on-idle #(raf f)))
|
(schedule-on-idle #(^function raf f)))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue