Split byte buffer helpers from types path impl

This commit is contained in:
Andrey Antukh 2025-05-22 13:52:51 +02:00
parent 2e0fd6ec1b
commit d9d2cc7b4e
4 changed files with 276 additions and 137 deletions

1
.gitignore vendored
View file

@ -30,6 +30,7 @@
/*.zip /*.zip
/.clj-kondo/.cache /.clj-kondo/.cache
/_dump /_dump
/notes
/backend/*.md /backend/*.md
/backend/*.sql /backend/*.sql
/backend/*.txt /backend/*.txt

View file

@ -0,0 +1,141 @@
;; 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 app.common.buffer
"A collection of helpers and macros for work with byte buffers"
(:refer-clojure :exclude [clone])
(:require
[app.common.uuid :as uuid])
#?(:cljs
(:require-macros [app.common.buffer])
:clj
(:import [java.nio ByteBuffer ByteOrder])))
(defmacro read-byte
[target offset]
(if (:ns &env)
`(.getInt8 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(long (.get ~target ~offset)))))
(defmacro read-bool
[target offset]
(if (:ns &env)
`(== 1 (.getInt8 ~target ~offset true))
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(== 1 (.get ~target ~offset)))))
(defmacro read-short
[target offset]
(if (:ns &env)
`(.getInt16 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.getShort ~target ~offset))))
(defmacro read-int
[target offset]
(if (:ns &env)
`(.getInt32 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(long (.getInt ~target ~offset)))))
(defmacro read-float
[target offset]
(if (:ns &env)
`(.getFloat32 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(double (.getFloat ~target ~offset)))))
(defmacro read-uuid
[target offset]
(if (:ns &env)
`(let [a# (.getUint32 ~target (+ ~offset 0) true)
b# (.getUint32 ~target (+ ~offset 4) true)
c# (.getUint32 ~target (+ ~offset 8) true)
d# (.getUint32 ~target (+ ~offset 12) true)]
(uuid/from-unsigned-parts a# b# c# d#))
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(try
(.order ~target ByteOrder/BIG_ENDIAN)
(let [msb# (.getLong ~target (+ ~offset 0))
lsb# (.getLong ~target (+ ~offset 8))]
(java.util.UUID. (long msb#) (long lsb#)))
(finally
(.order ~target ByteOrder/LITTLE_ENDIAN))))))
(defmacro write-byte
[target offset value]
(if (:ns &env)
`(.setInt8 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target ~offset (unchecked-byte ~value)))))
(defmacro write-short
[target offset value]
(if (:ns &env)
`(.setInt16 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putShort ~target ~offset (unchecked-short ~value)))))
(defmacro write-int
[target offset value]
(if (:ns &env)
`(.setInt32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putInt ~target ~offset (unchecked-int ~value)))))
(defmacro write-float
[target offset value]
(if (:ns &env)
`(.setFloat32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putFloat ~target ~offset (unchecked-float ~value)))))
(defmacro write-uuid
[target offset value]
(if (:ns &env)
`(let [barray# (uuid/get-u32 ~value)]
(.setUint32 ~target (+ ~offset 0) (aget barray# 0) true)
(.setUint32 ~target (+ ~offset 4) (aget barray# 1) true)
(.setUint32 ~target (+ ~offset 8) (aget barray# 2) true)
(.setUint32 ~target (+ ~offset 12) (aget barray# 3) true))
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})
value (with-meta value {:tag 'java.util.UUID})]
`(try
(.order ~target ByteOrder/BIG_ENDIAN)
(.putLong ~target (+ ~offset 0) (.getMostSignificantBits ~value))
(.putLong ~target (+ ~offset 8) (.getLeastSignificantBits ~value))
(finally
(.order ~target ByteOrder/LITTLE_ENDIAN))))))
(defn allocate
[size]
#?(:clj (let [buffer (ByteBuffer/allocate (int size))]
(.order buffer ByteOrder/LITTLE_ENDIAN))
:cljs (new js/DataView (new js/ArrayBuffer size))))
(defn clone
[buffer]
#?(:clj
(let [src (.array ^ByteBuffer buffer)
len (alength ^bytes src)
dst (byte-array len)]
(System/arraycopy src 0 dst 0 len)
(let [buffer (ByteBuffer/wrap dst)]
(.order buffer ByteOrder/LITTLE_ENDIAN)))
:cljs
(let [src-view (js/Uint32Array. buffer)
dst-buff (js/ArrayBuffer. (.-byteLength buffer))
dst-view (js/Uint32Array. dst-buff)]
(.set dst-view src-view)
dst-buff)))
(defn buffer?
[o]
#?(:clj (instance? ByteBuffer o)
:cljs (instance? js/DataView o)))

View file

@ -7,13 +7,13 @@
(ns app.common.types.path.impl (ns app.common.types.path.impl
"Contains schemas and data type implementation for PathData binary "Contains schemas and data type implementation for PathData binary
and plain formats" and plain formats"
#?(:cljs
(:require-macros [app.common.types.path.impl :refer [read-float read-short write-float write-short]]))
(:refer-clojure :exclude [-lookup -reduce]) (:refer-clojure :exclude [-lookup -reduce])
#?(:cljs (:require-macros [app.common.types.path.impl]))
(:require (:require
#?(:clj [app.common.fressian :as fres]) #?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as json]) #?(:clj [clojure.data.json :as json])
#?(:cljs [app.common.weak-map :as weak-map]) #?(:cljs [app.common.weak-map :as weak-map])
[app.common.buffer :as buf]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.generators :as sg] [app.common.schema.generators :as sg]
@ -42,93 +42,42 @@
;; IMPL HELPERS ;; IMPL HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro read-short
[target offset]
(if (:ns &env)
`(.getInt16 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.getShort ~target ~offset))))
(defmacro read-float
[target offset]
(if (:ns &env)
`(.getFloat32 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(double (.getFloat ~target ~offset)))))
(defmacro write-float
[target offset value]
(if (:ns &env)
`(.setFloat32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putFloat ~target ~offset ~value))))
(defmacro write-short
[target offset value]
(if (:ns &env)
`(.setInt16 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putShort ~target ~offset ~value))))
(defmacro with-cache (defmacro with-cache
"A helper macro that facilitates cache handling for content "A helper macro that facilitates cache handling for content
instance, only relevant on CLJS" instance, only relevant on CLJS"
[target key & expr] [target key & expr]
(if (:ns &env) (if (:ns &env)
(let [cache (gensym "cache-") (let [target (with-meta target {:tag 'js})]
target (with-meta target {:tag 'js})] `(let [~'cache (.-cache ~target)
`(let [~cache (.-cache ~target) ~'result (.get ~'cache ~key)]
~'result (.get ~cache ~key)]
(if ~'result (if ~'result
(do (do
~'result) ~'result)
(let [~'result (do ~@expr)] (let [~'result (do ~@expr)]
(.set ~cache ~key ~'result) (.set ~'cache ~key ~'result)
~'result)))) ~'result))))
`(do ~@expr))) `(do ~@expr)))
(defn- allocate
[n-segments]
#?(:clj (let [buffer (ByteBuffer/allocate (* n-segments SEGMENT-BYTE-SIZE))]
(.order buffer ByteOrder/LITTLE_ENDIAN))
:cljs (new js/ArrayBuffer (* n-segments SEGMENT-BYTE-SIZE))))
(defn- clone-buffer
[buffer]
#?(:clj
(let [src (.array ^ByteBuffer buffer)
len (alength ^bytes src)
dst (byte-array len)]
(System/arraycopy src 0 dst 0 len)
(let [buffer (ByteBuffer/wrap dst)]
(.order buffer ByteOrder/LITTLE_ENDIAN)))
:cljs
(let [src-view (js/Uint32Array. buffer)
dst-buff (js/ArrayBuffer. (.-byteLength buffer))
dst-view (js/Uint32Array. dst-buff)]
(.set dst-view src-view)
dst-buff)))
(defn- impl-transform-segment (defn- impl-transform-segment
"Apply a transformation to a segment located under specified offset" "Apply a transformation to a segment located under specified offset"
[buffer offset a b c d e f] [buffer offset a b c d e f]
(let [t (read-short buffer offset)] (let [t (buf/read-short buffer offset)]
(case t (case t
(1 2) (1 2)
(let [x (read-float buffer (+ offset 20)) (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
x (+ (* x a) (* y c) e) x (+ (* x a) (* y c) e)
y (+ (* x b) (* y d) f)] y (+ (* x b) (* y d) f)]
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
3 3
(let [c1x (read-float buffer (+ offset 4)) (let [c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
c1x (+ (* c1x a) (* c1y c) e) c1x (+ (* c1x a) (* c1y c) e)
c1y (+ (* c1x b) (* c1y d) f) c1y (+ (* c1x b) (* c1y d) f)
@ -137,12 +86,12 @@
x (+ (* x a) (* y c) e) x (+ (* x a) (* y c) e)
y (+ (* x b) (* y d) f)] y (+ (* x b) (* y d) f)]
(write-float buffer (+ offset 4) c1x) (buf/write-float buffer (+ offset 4) c1x)
(write-float buffer (+ offset 8) c1y) (buf/write-float buffer (+ offset 8) c1y)
(write-float buffer (+ offset 12) c2x) (buf/write-float buffer (+ offset 12) c2x)
(write-float buffer (+ offset 16) c2y) (buf/write-float buffer (+ offset 16) c2y)
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
nil))) nil)))
@ -166,13 +115,13 @@
result (transient initial)] result (transient initial)]
(if (< index size) (if (< index size)
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset) type (buf/read-short buffer offset)
c1x (read-float buffer (+ offset 4)) c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
type (case type type (case type
1 :line-to 1 :line-to
2 :move-to 2 :move-to
@ -191,13 +140,13 @@
result initial] result initial]
(if (< index size) (if (< index size)
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset) type (buf/read-short buffer offset)
c1x (read-float buffer (+ offset 4)) c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
type (case type type (case type
1 :line-to 1 :line-to
2 :move-to 2 :move-to
@ -212,13 +161,13 @@
(defn impl-lookup (defn impl-lookup
[buffer index f] [buffer index f]
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset) type (buf/read-short buffer offset)
c1x (read-float buffer (+ offset 4)) c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
type (case type type (case type
1 :line-to 1 :line-to
2 :move-to 2 :move-to
@ -230,27 +179,27 @@
(defn- to-string-segment* (defn- to-string-segment*
[buffer offset type ^StringBuilder builder] [buffer offset type ^StringBuilder builder]
(case (long type) (case (long type)
1 (let [x (read-float buffer (+ offset 20)) 1 (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
(doto builder (doto builder
(.append "M") (.append "M")
(.append x) (.append x)
(.append ",") (.append ",")
(.append y))) (.append y)))
2 (let [x (read-float buffer (+ offset 20)) 2 (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
(doto builder (doto builder
(.append "L") (.append "L")
(.append x) (.append x)
(.append ",") (.append ",")
(.append y))) (.append y)))
3 (let [c1x (read-float buffer (+ offset 4)) 3 (let [c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
(doto builder (doto builder
(.append "C") (.append "C")
(.append c1x) (.append c1x)
@ -275,7 +224,7 @@
(loop [index 0] (loop [index 0]
(when (< index size) (when (< index size)
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset)] type (buf/read-short buffer offset)]
(to-string-segment* buffer offset type builder) (to-string-segment* buffer offset type builder)
(recur (inc index))))) (recur (inc index)))))
@ -285,26 +234,26 @@
"Read segment from binary buffer at specified index" "Read segment from binary buffer at specified index"
[buffer index] [buffer index]
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset)] type (buf/read-short buffer offset)]
(case (long type) (case (long type)
1 (let [x (read-float buffer (+ offset 20)) 1 (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
{:command :move-to {:command :move-to
:params {:x (double x) :params {:x (double x)
:y (double y)}}) :y (double y)}})
2 (let [x (read-float buffer (+ offset 20)) 2 (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
{:command :line-to {:command :line-to
:params {:x (double x) :params {:x (double x)
:y (double y)}}) :y (double y)}})
3 (let [c1x (read-float buffer (+ offset 4)) 3 (let [c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
{:command :curve-to {:command :curve-to
:params {:x (double x) :params {:x (double x)
:y (double y) :y (double y)
@ -339,7 +288,7 @@
ITransformable ITransformable
(-transform [_ m] (-transform [_ m]
(let [buffer (clone-buffer buffer)] (let [buffer (buf/clone buffer)]
(impl-transform buffer m size) (impl-transform buffer m size)
(PathData. size buffer nil))) (PathData. size buffer nil)))
@ -427,7 +376,7 @@
ITransformable ITransformable
(-transform [this m] (-transform [this m]
(let [buffer (clone-buffer buffer) (let [buffer (buf/clone buffer)
dview (js/DataView. buffer)] dview (js/DataView. buffer)]
(impl-transform dview m size) (impl-transform dview m size)
(PathData. size buffer dview (weak-map/create) nil))) (PathData. size buffer dview (weak-map/create) nil)))
@ -688,10 +637,8 @@
[segments] [segments]
(assert (check-segments segments)) (assert (check-segments segments))
(let [total (count segments) (let [total (count segments)
#?@(:cljs [buffer' (allocate total) buffer (buf/allocate (* total SEGMENT-BYTE-SIZE))]
buffer (new js/DataView buffer')]
:clj [buffer (allocate total)])]
(loop [index 0] (loop [index 0]
(when (< index total) (when (< index total)
(let [segment (nth segments index) (let [segment (nth segments index)
@ -701,18 +648,18 @@
(let [params (get segment :params) (let [params (get segment :params)
x (float (get params :x)) x (float (get params :x))
y (float (get params :y))] y (float (get params :y))]
(write-short buffer offset 1) (buf/write-short buffer offset 1)
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
:line-to :line-to
(let [params (get segment :params) (let [params (get segment :params)
x (float (get params :x)) x (float (get params :x))
y (float (get params :y))] y (float (get params :y))]
(write-short buffer offset 2) (buf/write-short buffer offset 2)
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
:curve-to :curve-to
(let [params (get segment :params) (let [params (get segment :params)
@ -723,16 +670,16 @@
c2x (float (get params :c2x x)) c2x (float (get params :c2x x))
c2y (float (get params :c2y y))] c2y (float (get params :c2y y))]
(write-short buffer offset 3) (buf/write-short buffer offset 3)
(write-float buffer (+ offset 4) c1x) (buf/write-float buffer (+ offset 4) c1x)
(write-float buffer (+ offset 8) c1y) (buf/write-float buffer (+ offset 8) c1y)
(write-float buffer (+ offset 12) c2x) (buf/write-float buffer (+ offset 12) c2x)
(write-float buffer (+ offset 16) c2y) (buf/write-float buffer (+ offset 16) c2y)
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
:close-path :close-path
(write-short buffer offset 4)) (buf/write-short buffer offset 4))
(recur (inc index))))) (recur (inc index)))))
(from-bytes buffer))) (from-bytes buffer)))

View file

@ -0,0 +1,50 @@
;; 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.buffer-test
(:require
[app.common.buffer :as buf]
[app.common.uuid :as uuid]
[clojure.test :as t]))
(t/deftest allocate
(let [b (buf/allocate 1)]
(t/is (buf/buffer? b))))
(t/deftest rw-byte
(let [b (buf/allocate 1)]
(buf/write-byte b 0 123)
(let [res (buf/read-byte b 0)]
(t/is (= 123 res)))
(buf/write-byte b 0 252)
(let [res (buf/read-byte b 0)]
(t/is (= -4 res)))))
(t/deftest rw-int
(let [b (buf/allocate 4)]
(buf/write-int b 0 123)
(let [res (buf/read-int b 0)]
(t/is (= 123 res)))))
(t/deftest rw-float
(let [b (buf/allocate 4)]
(buf/write-float b 0 123)
(let [res (buf/read-float b 0)]
(t/is (= 123.0 res)))))
(t/deftest rw-short
(let [b (buf/allocate 2)]
(buf/write-short b 0 123)
(let [res (buf/read-short b 0)]
(t/is (= 123 res)))))
(t/deftest rw-uuid
(let [b (buf/allocate 16)
id (uuid/next)]
(buf/write-uuid b 0 id)
(let [res (buf/read-uuid b 0)]
(t/is (= id res)))))