mirror of
https://github.com/penpot/penpot.git
synced 2025-02-02 04:19:08 -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"
|
||||
:rfn (fn [value]
|
||||
(js/parseInt value 10))})
|
||||
|
||||
#?(:cljs
|
||||
{:id "u"
|
||||
:rfn parse-uuid})
|
||||
|
|
|
@ -17,32 +17,32 @@
|
|||
java.util.UUID
|
||||
java.nio.ByteBuffer)))
|
||||
|
||||
(def zero #uuid "00000000-0000-0000-0000-000000000000")
|
||||
|
||||
(defn zero?
|
||||
[v]
|
||||
(= zero v))
|
||||
(defn uuid
|
||||
"Parse string uuid representation into proper UUID instance."
|
||||
[s]
|
||||
#?(:clj (UUID/fromString s)
|
||||
:cljs (c/uuid s)))
|
||||
|
||||
(defn next
|
||||
[]
|
||||
#?(:clj (UUIDv8/create)
|
||||
:cljs (impl/v8)))
|
||||
:cljs (uuid (impl/v8))))
|
||||
|
||||
(defn random
|
||||
"Alias for clj-uuid/v4."
|
||||
[]
|
||||
#?(:clj (UUID/randomUUID)
|
||||
:cljs (impl/v4)))
|
||||
|
||||
(defn uuid
|
||||
"Parse string uuid representation into proper UUID instance."
|
||||
[s]
|
||||
#?(:clj (UUID/fromString s)
|
||||
:cljs (c/parse-uuid s)))
|
||||
:cljs (uuid (impl/v4))))
|
||||
|
||||
(defn custom
|
||||
([a] #?(:clj (UUID. 0 a) :cljs (c/parse-uuid (impl/custom 0 a))))
|
||||
([b a] #?(:clj (UUID. b a) :cljs (c/parse-uuid (impl/custom b a)))))
|
||||
([a] #?(:clj (UUID. 0 a) :cljs (uuid (impl/custom 0 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
|
||||
(defn get-word-high
|
||||
|
@ -54,32 +54,41 @@
|
|||
[id]
|
||||
(.getLeastSignificantBits ^UUID id)))
|
||||
|
||||
#?(:clj
|
||||
(defn get-bytes
|
||||
[^UUID o]
|
||||
(defn get-bytes
|
||||
[^UUID o]
|
||||
#?(:clj
|
||||
(let [buf (ByteBuffer/allocate 16)]
|
||||
(.putLong buf (.getMostSignificantBits o))
|
||||
(.putLong buf (.getLeastSignificantBits o))
|
||||
(.array buf))))
|
||||
(.array buf))
|
||||
:cljs
|
||||
(impl/getBytes (.-uuid o))))
|
||||
|
||||
#?(:clj
|
||||
(defn from-bytes
|
||||
[^bytes o]
|
||||
(defn from-bytes
|
||||
[^bytes o]
|
||||
#?(:clj
|
||||
(let [buf (ByteBuffer/wrap o)]
|
||||
(UUID. ^long (.getLong buf)
|
||||
^long (.getLong buf)))))
|
||||
^long (.getLong buf)))
|
||||
:cljs
|
||||
(uuid (impl/fromBytes o))))
|
||||
|
||||
#?(:cljs
|
||||
(defn uuid->short-id
|
||||
"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"
|
||||
[id]
|
||||
(impl/short-v8 (dm/str id))))
|
||||
(impl/shortV8 (dm/str id))))
|
||||
|
||||
#?(:cljs
|
||||
(defn uuid->u32
|
||||
[id]
|
||||
(impl/get-u32 (dm/str id))))
|
||||
(defn get-u32
|
||||
[this]
|
||||
(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
|
||||
(defn hash-int
|
||||
|
@ -88,4 +97,3 @@
|
|||
b (.getLeastSignificantBits ^UUID id)]
|
||||
(+ (clojure.lang.Murmur3/hashLong a)
|
||||
(clojure.lang.Murmur3/hashLong b)))))
|
||||
|
||||
|
|
|
@ -7,12 +7,10 @@
|
|||
*/
|
||||
"use strict";
|
||||
|
||||
goog.require("cljs.core");
|
||||
goog.require("app.common.encoding_impl");
|
||||
goog.provide("app.common.uuid_impl");
|
||||
|
||||
goog.scope(function() {
|
||||
const core = cljs.core;
|
||||
const global = goog.global;
|
||||
const encoding = app.common.encoding_impl;
|
||||
const self = app.common.uuid_impl;
|
||||
|
@ -129,7 +127,7 @@ goog.scope(function() {
|
|||
fill(arr);
|
||||
arr[6] = (arr[6] & 0x0f) | 0x40;
|
||||
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, 8, lsb, false);
|
||||
|
||||
return core.uuid(encoding.bufferToHex(int8, true));
|
||||
return encoding.bufferToHex(int8, true);
|
||||
};
|
||||
|
||||
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) => {
|
||||
tag = BigInt.asUintN(64, "" + tag);
|
||||
if (tag > 0x0000_0000_0000_000fn) {
|
||||
|
@ -207,10 +210,11 @@ goog.scope(function() {
|
|||
|
||||
factory.create = create;
|
||||
factory.setTag = setTag;
|
||||
factory.fromArray = fromArray;
|
||||
return factory;
|
||||
})();
|
||||
|
||||
self.short_v8 = function(uuid) {
|
||||
self.shortV8 = function(uuid) {
|
||||
const buff = encoding.hexToBuffer(uuid);
|
||||
const short = new Uint8Array(buff, 4);
|
||||
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)}`;
|
||||
};
|
||||
|
||||
self.get_u32 = (function() {
|
||||
const UUID_BYTE_SIZE = 16;
|
||||
const ab = new ArrayBuffer(UUID_BYTE_SIZE);
|
||||
const u32buffer = new Uint32Array(ab);
|
||||
const HYPHEN = '-'.charCodeAt(0);
|
||||
const A = 'a'.charCodeAt(0);
|
||||
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 {
|
||||
digit = charCode - ZERO;
|
||||
}
|
||||
numDigit++;
|
||||
const bitPos = (MAX_DIGIT - numDigit) * HALF_BITS;
|
||||
u32 |= (digit << bitPos);
|
||||
if (numDigit === MAX_DIGIT) {
|
||||
u32buffer[u32index++] = u32;
|
||||
u32 = 0;
|
||||
numDigit = 0;
|
||||
}
|
||||
self.fromBytes = function(data) {
|
||||
if (data instanceof Uint8Array) {
|
||||
return self.v8.fromArray(data);
|
||||
} else if (data instanceof Int8Array) {
|
||||
data = Uint8Array.from(data);
|
||||
return self.v8.fromArray(data);
|
||||
} else {
|
||||
let buffer = data?.buffer;
|
||||
if (buffer instanceof ArrayBuffer) {
|
||||
data = new Uint8Array(buffer);
|
||||
return self.v8.fromArray(data);
|
||||
} else {
|
||||
throw new Error("invalid array type received");
|
||||
}
|
||||
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
|
||||
[id]
|
||||
(let [buffer (uuid/uuid->u32 id)
|
||||
(let [buffer (uuid/get-u32 id)
|
||||
create-shape (unchecked-get internal-module "_create_shape")]
|
||||
(^function create-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
|
||||
|
||||
(defn use-shape
|
||||
[id]
|
||||
(let [buffer (uuid/uuid->u32 id)
|
||||
(let [buffer (uuid/get-u32 id)
|
||||
use-shape (unchecked-get internal-module "_use_shape")]
|
||||
(^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")]
|
||||
(^function clear-shape-children)
|
||||
(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))))))
|
||||
|
||||
(defn set-shape-fills
|
||||
|
|
|
@ -61,14 +61,16 @@
|
|||
(cancel-idle-callback sem))))))
|
||||
|
||||
(def ^:private request-animation-frame
|
||||
(or (and (exists? js/window) (.-requestAnimationFrame js/window))
|
||||
#(js/setTimeout % 16)))
|
||||
(if (and (exists? js/globalThis)
|
||||
(exists? (.-requestAnimationFrame js/globalThis)))
|
||||
#(.requestAnimationFrame js/globalThis %)
|
||||
#(js/setTimeout % 16)))
|
||||
|
||||
(defn raf
|
||||
[f]
|
||||
(request-animation-frame f))
|
||||
(^function request-animation-frame f))
|
||||
|
||||
(defn idle-then-raf
|
||||
[f]
|
||||
(schedule-on-idle #(raf f)))
|
||||
(schedule-on-idle #(^function raf f)))
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue