0
Fork 0
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:
Alejandro 2024-11-18 15:47:48 +01:00 committed by GitHub
commit 4e28f1b1f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 157 additions and 73 deletions

View file

@ -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})

View file

@ -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)))))

View file

@ -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);
} }
})();
}); });

View 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))))))

View file

@ -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

View file

@ -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)))