0
Fork 0
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:
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"
:rfn (fn [value]
(js/parseInt value 10))})
#?(:cljs
{:id "u"
:rfn parse-uuid})

View file

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

View file

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

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

View file

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