From d364f4db6283df7bd7376eca4aa61dd3dfed1bbe Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 11 Aug 2025 13:39:09 +0200 Subject: [PATCH 01/10] :recycle: Sanitize heap write and read operations Mainly improves the offset management making it less error prone, encapsulating the write operation and offeset management into write-* operations with proper asserts for the expected heap type. --- common/src/app/common/types/path/impl.cljc | 1 + .../app/main/data/workspace/modifiers.cljs | 59 ++-- frontend/src/app/render_wasm/api.cljs | 333 +++++++----------- .../src/app/render_wasm/deserializers.cljs | 37 +- frontend/src/app/render_wasm/mem.cljs | 20 +- frontend/src/app/render_wasm/serializers.cljs | 33 +- 6 files changed, 226 insertions(+), 257 deletions(-) diff --git a/common/src/app/common/types/path/impl.cljc b/common/src/app/common/types/path/impl.cljc index 6e513f0057..bae9d4840d 100644 --- a/common/src/app/common/types/path/impl.cljc +++ b/common/src/app/common/types/path/impl.cljc @@ -29,6 +29,7 @@ #?(:clj (set! *warn-on-reflection* true)) (def ^:const SEGMENT-U8-SIZE 28) +(def ^:const SEGMENT-U32-SIZE (/ SEGMENT-U8-SIZE 4)) (defprotocol IPathData (-write-to [_ buffer offset] "write the content to the specified buffer") diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 382320d6ea..74b0ea8c7d 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -530,25 +530,24 @@ nil))))))) modif-tree)) + +(def ^:private xf:parse-geometry-modifier + (let [default-transform (gmt/matrix)] + (keep (fn [[id data]] + (cond + (= id uuid/zero) + nil + + (ctm/has-geometry? (:modifiers data)) + (d/vec2 id (ctm/modifiers->transform (:modifiers data))) + + ;; Unit matrix is used for reflowing + :else + (d/vec2 id default-transform)))))) + (defn- parse-geometry-modifiers [modif-tree] - (into - [] - (keep - (fn [[id data]] - (cond - (= id uuid/zero) - nil - - (ctm/has-geometry? (:modifiers data)) - {:id id - :transform (ctm/modifiers->transform (:modifiers data))} - - ;; Unit matrix is used for reflowing - :else - {:id id - :transform (gmt/matrix)}))) - modif-tree)) + (into [] xf:parse-geometry-modifier modif-tree)) (defn- extract-property-changes [modif-tree] @@ -573,6 +572,8 @@ (update [_ state] (assoc state :workspace-wasm-modifiers modifiers)))) +(def ^:private xf:map-key (map key)) + #_:clj-kondo/ignore (defn set-wasm-modifiers [modif-tree & {:keys [ignore-constraints ignore-snap-pixel] @@ -591,16 +592,17 @@ (watch [_ state _] (wasm.api/clean-modifiers) (let [prev-wasm-props (:prev-wasm-props state) - wasm-props (:wasm-props state) - objects (dsh/lookup-page-objects state) + wasm-props (:wasm-props state) + objects (dsh/lookup-page-objects state) pixel-precision false] (set-wasm-props! objects prev-wasm-props wasm-props) (let [structure-entries (parse-structure-modifiers modif-tree)] (wasm.api/set-structure-modifiers structure-entries) (let [geometry-entries (parse-geometry-modifiers modif-tree) - modifiers (wasm.api/propagate-modifiers geometry-entries pixel-precision)] + modifiers (wasm.api/propagate-modifiers geometry-entries pixel-precision)] (wasm.api/set-modifiers modifiers) - (let [selrect (wasm.api/get-selection-rect (->> geometry-entries (map :id)))] + (let [ids (into [] xf:map-key geometry-entries) + selrect (wasm.api/get-selection-rect ids)] (rx/of (set-temporary-selrect selrect) (set-temporary-modifiers modifiers))))))))) @@ -649,16 +651,14 @@ ;; way we don't have to check all the attributes (assoc :attrs transform-attrs)) - geometry-entries (parse-geometry-modifiers modif-tree) + geometry-entries + (parse-geometry-modifiers modif-tree) snap-pixel? (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) transforms - (into - {} - (map (fn [{:keys [id transform]}] [id transform])) - (wasm.api/propagate-modifiers geometry-entries snap-pixel?)) + (into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?)) modif-tree (propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state)) @@ -709,10 +709,9 @@ (gm/set-objects-modifiers objects)) modifiers - (->> modif-tree - (map (fn [[id {:keys [modifiers]}]] - {:id id - :transform (ctm/modifiers->transform modifiers)})))] + (mapv (fn [[id {:keys [modifiers]}]] + (d/vec2 id (ctm/modifiers->transform modifiers))) + modif-tree)] (wasm.api/set-modifiers modifiers)))))) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 1faa24b187..43cd169e6b 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -10,8 +10,6 @@ ["react-dom/server" :as rds] [app.common.data :as d :refer [not-empty?]] [app.common.data.macros :as dm] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] [app.common.types.fills :as types.fills] [app.common.types.fills.impl :as types.fills.impl] [app.common.types.path :as path] @@ -41,39 +39,17 @@ (def use-dpr? (contains? cf/flags :render-wasm-dpr)) -;; -;; List of common entry sizes. -;; -;; All of these entries are in bytes so we need to adjust -;; these values to work with TypedArrays of 32 bits. -;; (def ^:const UUID-U8-SIZE 16) (def ^:const UUID-U32-SIZE (/ UUID-U8-SIZE 4)) (def ^:const MODIFIER-U8-SIZE 40) +(def ^:const MODIFIER-U32-SIZE (/ MODIFIER-U8-SIZE 4)) (def ^:const MODIFIER-TRANSFORM-U8-OFFSET-SIZE 16) (def ^:const GRID-LAYOUT-ROW-U8-SIZE 5) (def ^:const GRID-LAYOUT-COLUMN-U8-SIZE 5) (def ^:const GRID-LAYOUT-CELL-U8-SIZE 37) -(defn modifier-get-entries-size - "Returns the list of a modifier list in bytes" - [modifiers] - (mem/get-list-size modifiers MODIFIER-U8-SIZE)) - -(defn grid-layout-get-row-entries-size - [rows] - (mem/get-list-size rows GRID-LAYOUT-ROW-U8-SIZE)) - -(defn grid-layout-get-column-entries-size - [columns] - (mem/get-list-size columns GRID-LAYOUT-COLUMN-U8-SIZE)) - -(defn grid-layout-get-cell-entries-size - [cells] - (mem/get-list-size cells GRID-LAYOUT-CELL-U8-SIZE)) - (def dpr (if use-dpr? (if (exists? js/window) js/window.devicePixelRatio 1.0) 1.0)) @@ -172,20 +148,20 @@ (defn set-shape-children [children] - (let [heap (mem/get-heap-u32) - length (count children)] - (perf/begin-measure "set-shape-children") - (when (pos? length) - (let [offset (mem/alloc->offset-32 (* UUID-U8-SIZE length))] - (reduce (fn [offset id] - (sr/heapu32-set-uuid id heap offset) - (+ offset UUID-U32-SIZE)) - offset - children))) - (let [result (h/call wasm/internal-module "_set_children")] - (perf/end-measure "set-shape-children") - result))) + (when-not ^boolean (empty? children) + (perf/begin-measure "set-shape-children") + (let [heap (mem/get-heap-u32) + size (mem/get-alloc-size children UUID-U8-SIZE) + offset (mem/alloc->offset-32 size)] + (reduce (fn [offset id] + (sr/write-uuid offset heap id)) + offset + children) + + (let [result (h/call wasm/internal-module "_set_children")] + (perf/end-measure "set-shape-children") + result)))) (defn- get-string-length [string] @@ -467,14 +443,11 @@ (defn set-grid-layout-rows [entries] - (let [size (grid-layout-get-row-entries-size entries) + (let [size (mem/get-alloc-size entries GRID-LAYOUT-ROW-U8-SIZE) offset (mem/alloc size) + heap (-> (mem/get-heap-u8) + (mem/view offset size))] - heap - (js/Uint8Array. - (.-buffer (mem/get-heap-u8)) - offset - size)] (loop [entries (seq entries) current-offset 0] (when-not (empty? entries) @@ -486,14 +459,11 @@ (defn set-grid-layout-columns [entries] - (let [size (grid-layout-get-column-entries-size entries) + (let [size (mem/get-alloc-size entries GRID-LAYOUT-COLUMN-U8-SIZE) offset (mem/alloc size) + heap (-> (mem/get-heap-u8) + (mem/view offset size))] - heap - (js/Uint8Array. - (.-buffer (mem/get-heap-u8)) - offset - size)] (loop [entries (seq entries) current-offset 0] (when-not (empty? entries) @@ -506,14 +476,10 @@ (defn set-grid-layout-cells [cells] (let [entries (vals cells) - size (grid-layout-get-cell-entries-size entries) - offset (mem/alloc size) - - heap - (js/Uint8Array. - (.-buffer (mem/get-heap-u8)) - offset - size)] + size (mem/get-alloc-size entries GRID-LAYOUT-CELL-U8-SIZE) + offset (mem/alloc size) + heap (-> (mem/get-heap-u8) + (mem/view offset size))] (loop [entries (seq entries) current-offset 0] @@ -625,7 +591,7 @@ (h/call wasm/internal-module "_add_shape_shadow" rgba blur spread x y (sr/translate-shadow-style style) hidden) (recur (inc index))))))) -(declare propagate-apply) +;; (declare propagate-apply) (defn set-shape-text-content [shape-id content] @@ -824,134 +790,90 @@ (defn set-focus-mode [entries] - (let [offset (mem/alloc->offset-32 (* (count entries) 16)) - heapu32 (mem/get-heap-u32)] + (when-not ^boolean (empty? entries) + (let [size (mem/get-alloc-size entries UUID-U8-SIZE) + heap (mem/get-heap-u32) + offset (mem/alloc->offset-32 size)] - (loop [entries (seq entries) - current-offset offset] - (when-not (empty? entries) - (let [id (first entries)] - (sr/heapu32-set-uuid id heapu32 current-offset) - (recur (rest entries) (+ current-offset (mem/->offset-32 16)))))) + (reduce (fn [offset id] + (sr/write-uuid offset heap id)) + offset + entries) - (h/call wasm/internal-module "_set_focus_mode") - (clear-drawing-cache) - (request-render "set-focus-mode"))) + (h/call wasm/internal-module "_set_focus_mode") + (clear-drawing-cache) + (request-render "set-focus-mode")))) (defn set-structure-modifiers [entries] - (when-not (empty? entries) - (let [offset (mem/alloc->offset-32 (mem/get-list-size entries 44)) + (when-not ^boolean (empty? entries) + (let [size (mem/get-alloc-size entries 44) + offset (mem/alloc->offset-32 size) heapu32 (mem/get-heap-u32) heapf32 (mem/get-heap-f32)] - (loop [entries (seq entries) - current-offset offset] - (when-not (empty? entries) - (let [{:keys [type parent id index value] :as entry} (first entries)] - (sr/heapu32-set-u32 (sr/translate-structure-modifier-type type) heapu32 (+ current-offset 0)) - (sr/heapu32-set-u32 (or index 0) heapu32 (+ current-offset 1)) - (sr/heapu32-set-uuid parent heapu32 (+ current-offset 2)) - (sr/heapu32-set-uuid id heapu32 (+ current-offset 6)) - (aset heapf32 (+ current-offset 10) value) - (recur (rest entries) (+ current-offset 11))))) + + + (reduce (fn [offset {:keys [type parent id index value]}] + (-> offset + (sr/write-u32 heapu32 (sr/translate-structure-modifier-type type)) + (sr/write-u32 heapu32 (d/nilv index 0)) + (sr/write-uuid heapu32 parent) + (sr/write-uuid heapu32 id) + (sr/write-f32 heapf32 value))) + offset + entries) + (h/call wasm/internal-module "_set_structure_modifiers")))) (defn propagate-modifiers [entries pixel-precision] - (when (d/not-empty? entries) - (let [offset (mem/alloc->offset-32 (modifier-get-entries-size entries)) - heapf32 (mem/get-heap-f32) - heapu32 (mem/get-heap-u32)] + (when-not ^boolean (empty? entries) + (let [heapf32 (mem/get-heap-f32) + heapu32 (mem/get-heap-u32) + size (mem/get-alloc-size entries MODIFIER-U8-SIZE) + offset (mem/alloc->offset-32 size)] - (loop [entries (seq entries) - current-offset offset] - (when-not (empty? entries) - (let [{:keys [id transform]} (first entries)] - (sr/heapu32-set-uuid id heapu32 current-offset) - (sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/->offset-32 MODIFIER-TRANSFORM-U8-OFFSET-SIZE))) - (recur (rest entries) (+ current-offset (mem/->offset-32 MODIFIER-U8-SIZE)))))) + (reduce (fn [offset [id transform]] + (-> offset + (sr/write-uuid heapu32 id) + (sr/write-matrix heapf32 transform))) + offset + entries) + + (let [offset (-> (h/call wasm/internal-module "_propagate_modifiers" pixel-precision) + (mem/->offset-32)) + length (aget heapu32 offset) + max-offset (+ offset 1 (* length MODIFIER-U32-SIZE)) + result (loop [result (transient []) + offset (inc offset)] + (if (< offset max-offset) + (let [entry (dr/read-modifier-entry heapu32 heapf32 offset)] + (recur (conj! result entry) + (+ offset MODIFIER-U32-SIZE))) + (persistent! result)))] - (let [result-offset (h/call wasm/internal-module "_propagate_modifiers" pixel-precision) - heapf32 (mem/get-heap-f32) - heapu32 (mem/get-heap-u32) - len (aget heapu32 (mem/->offset-32 result-offset)) - result - (->> (range 0 len) - (mapv #(dr/heap32->entry heapu32 heapf32 (mem/->offset-32 (+ result-offset 4 (* % MODIFIER-U8-SIZE))))))] (mem/free) - result)))) -(defn propagate-apply - [entries pixel-precision] - (when (d/not-empty? entries) - (let [offset (mem/alloc->offset-32 (modifier-get-entries-size entries)) - heapf32 (mem/get-heap-f32) - heapu32 (mem/get-heap-u32)] - - (loop [entries (seq entries) - current-offset offset] - (when-not (empty? entries) - (let [{:keys [id transform]} (first entries)] - (sr/heapu32-set-uuid id heapu32 current-offset) - (sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/->offset-32 MODIFIER-TRANSFORM-U8-OFFSET-SIZE))) - (recur (rest entries) (+ current-offset (mem/->offset-32 MODIFIER-U8-SIZE)))))) - - (let [offset (h/call wasm/internal-module "_propagate_apply" pixel-precision) - heapf32 (mem/get-heap-f32) - width (aget heapf32 (mem/->offset-32 (+ offset 0))) - height (aget heapf32 (mem/->offset-32 (+ offset 4))) - cx (aget heapf32 (mem/->offset-32 (+ offset 8))) - cy (aget heapf32 (mem/->offset-32 (+ offset 12))) - - a (aget heapf32 (mem/->offset-32 (+ offset 16))) - b (aget heapf32 (mem/->offset-32 (+ offset 20))) - c (aget heapf32 (mem/->offset-32 (+ offset 24))) - d (aget heapf32 (mem/->offset-32 (+ offset 28))) - e (aget heapf32 (mem/->offset-32 (+ offset 32))) - f (aget heapf32 (mem/->offset-32 (+ offset 36))) - transform (gmt/matrix a b c d e f)] - - (mem/free) - (request-render "set-modifiers") - - {:width width - :height height - :center (gpt/point cx cy) - :transform transform})))) - (defn get-selection-rect [entries] - (when (d/not-empty? entries) - (let [offset (mem/alloc->offset-32 (* (count entries) 16)) - heapu32 (mem/get-heap-u32)] - (loop [entries (seq entries) - current-offset offset] - (when-not (empty? entries) - (let [id (first entries)] - (sr/heapu32-set-uuid id heapu32 current-offset) - (recur (rest entries) (+ current-offset (mem/->offset-32 16)))))) + (when-not ^boolean (empty? entries) + (let [size (mem/get-alloc-size entries UUID-U8-SIZE) + offset (mem/alloc->offset-32 size) + heapu32 (mem/get-heap-u32) + heapf32 (mem/get-heap-f32)] - (let [offset (h/call wasm/internal-module "_get_selection_rect") - heap (mem/get-heap-f32) - width (aget heap (mem/->offset-32 (+ offset 0))) - height (aget heap (mem/->offset-32 (+ offset 4))) - cx (aget heap (mem/->offset-32 (+ offset 8))) - cy (aget heap (mem/->offset-32 (+ offset 12))) - a (aget heap (mem/->offset-32 (+ offset 16))) - b (aget heap (mem/->offset-32 (+ offset 20))) - c (aget heap (mem/->offset-32 (+ offset 24))) - d (aget heap (mem/->offset-32 (+ offset 28))) - e (aget heap (mem/->offset-32 (+ offset 32))) - f (aget heap (mem/->offset-32 (+ offset 36)))] + (reduce (fn [offset id] + (sr/write-uuid offset heapu32 id)) + offset + entries) + (let [offset (-> (h/call wasm/internal-module "_get_selection_rect") + (mem/->offset-32)) + result (dr/read-selection-rect heapf32 offset)] (mem/free) - {:width width - :height height - :center (gpt/point cx cy) - :transform (gmt/matrix a b c d e f)})))) - + result)))) (defn set-canvas-background [background] @@ -965,22 +887,26 @@ (defn set-modifiers [modifiers] - (when-not (empty? modifiers) - (let [offset (mem/alloc->offset-32 (* MODIFIER-U8-SIZE (count modifiers))) - heapu32 (mem/get-heap-u32) - heapf32 (mem/get-heap-f32)] - (loop [entries (seq modifiers) - current-offset offset] - (when-not (empty? entries) - (let [{:keys [id transform]} (first entries)] - (sr/heapu32-set-uuid id heapu32 current-offset) - (sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/->offset-32 MODIFIER-TRANSFORM-U8-OFFSET-SIZE))) - (recur (rest entries) (+ current-offset (mem/->offset-32 MODIFIER-U8-SIZE)))))) + ;; We need to ensure efficient operations + (assert (vector? modifiers) "expected a vector for `set-modifiers`") - (h/call wasm/internal-module "_set_modifiers") + (let [length (count modifiers)] + (when (pos? length) + (let [offset (mem/alloc->offset-32 (* MODIFIER-U8-SIZE length)) + heapu32 (mem/get-heap-u32) + heapf32 (mem/get-heap-f32)] - (request-render "set-modifiers")))) + (reduce (fn [offset [id transform]] + (-> offset + (sr/write-uuid heapu32 id) + (sr/write-matrix heapf32 transform))) + offset + modifiers) + + (h/call wasm/internal-module "_set_modifiers") + + (request-render "set-modifiers"))))) (defn initialize [base-objects zoom vbox background] @@ -1062,41 +988,42 @@ (defn shape-to-path [id] (use-shape id) - (let [offset (h/call wasm/internal-module "_current_to_path") - offset (mem/->offset-32 offset) - heapu32 (mem/get-heap-u32) - - length (aget heapu32 offset) - data (mem/slice heapu32 - (+ offset 1) - (+ offset 1 (* length (/ path.impl/SEGMENT-U8-SIZE 4)))) + (let [offset (-> (h/call wasm/internal-module "_current_to_path") + (mem/->offset-32)) + heap (mem/get-heap-u32) + length (aget heap offset) + data (mem/slice heap + (+ offset 1) + (* length path.impl/SEGMENT-U32-SIZE)) content (path/from-bytes data)] (mem/free) content)) +(defn- calculate-bool* + [bool-type] + (-> (h/call wasm/internal-module "_calculate_bool" (sr/translate-bool-type bool-type)) + (mem/->offset-32))) + (defn calculate-bool [bool-type ids] - (let [num-ids (count ids) - offset (mem/alloc->offset-32 (* UUID-U8-SIZE num-ids)) - heap (mem/get-heap-u32)] + + (let [size (mem/get-alloc-size ids UUID-U8-SIZE) + heap (mem/get-heap-u32) + offset (mem/alloc->offset-32 size)] (reduce (fn [offset id] - (sr/heapu32-set-uuid id heap offset) - (+ offset UUID-U32-SIZE)) + (sr/write-uuid offset heap id)) offset - (rseq ids))) + (rseq ids)) - (let [offset (h/call wasm/internal-module "_calculate_bool" (sr/translate-bool-type bool-type)) - offset (mem/->offset-32 offset) - heapu32 (mem/get-heap-u32) - - length (aget heapu32 offset) - data (mem/slice heapu32 - (+ offset 1) - (+ offset 1 (* length (/ path.impl/SEGMENT-U8-SIZE 4)))) - content (path/from-bytes data)] - (mem/free) - content)) + (let [offset (calculate-bool* bool-type) + length (aget heap offset) + data (mem/slice heap + (+ offset 1) + (* length path.impl/SEGMENT-U32-SIZE)) + content (path/from-bytes data)] + (mem/free) + content))) (defonce module (delay diff --git a/frontend/src/app/render_wasm/deserializers.cljs b/frontend/src/app/render_wasm/deserializers.cljs index 3c7f1f16c0..dd718d82c4 100644 --- a/frontend/src/app/render_wasm/deserializers.cljs +++ b/frontend/src/app/render_wasm/deserializers.cljs @@ -5,25 +5,44 @@ ;; Copyright (c) KALEIDOS INC (ns app.render-wasm.deserializers (:require + [app.common.data :as d] [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] [app.common.uuid :as uuid])) -(defn heap32->entry +(defn read-modifier-entry [heapu32 heapf32 offset] (let [id1 (aget heapu32 (+ offset 0)) id2 (aget heapu32 (+ offset 1)) id3 (aget heapu32 (+ offset 2)) id4 (aget heapu32 (+ offset 3)) - a (aget heapf32 (+ offset 4)) - b (aget heapf32 (+ offset 5)) - c (aget heapf32 (+ offset 6)) - d (aget heapf32 (+ offset 7)) - e (aget heapf32 (+ offset 8)) - f (aget heapf32 (+ offset 9)) + a (aget heapf32 (+ offset 4)) + b (aget heapf32 (+ offset 5)) + c (aget heapf32 (+ offset 6)) + d (aget heapf32 (+ offset 7)) + e (aget heapf32 (+ offset 8)) + f (aget heapf32 (+ offset 9))] - id (uuid/from-unsigned-parts id1 id2 id3 id4)] + (d/vec2 (uuid/from-unsigned-parts id1 id2 id3 id4) + (gmt/matrix a b c d e f)))) - {:id id + +(defn read-selection-rect + [heapf32 offset] + (let [width (aget heapf32 (+ offset 0)) + height (aget heapf32 (+ offset 1)) + cx (aget heapf32 (+ offset 2)) + cy (aget heapf32 (+ offset 3)) + a (aget heapf32 (+ offset 4)) + b (aget heapf32 (+ offset 5)) + c (aget heapf32 (+ offset 6)) + d (aget heapf32 (+ offset 7)) + e (aget heapf32 (+ offset 8)) + f (aget heapf32 (+ offset 9))] + {:width width + :height height + :center (gpt/point cx cy) :transform (gmt/matrix a b c d e f)})) + diff --git a/frontend/src/app/render_wasm/mem.cljs b/frontend/src/app/render_wasm/mem.cljs index fec386b5a5..e56ec4a01f 100644 --- a/frontend/src/app/render_wasm/mem.cljs +++ b/frontend/src/app/render_wasm/mem.cljs @@ -15,10 +15,12 @@ ;; Divides the value by 4 (bit-shift-right value 2)) -(defn get-list-size - "Returns the size of a list in bytes" - [list list-item-size] - (* list-item-size (count list))) +(defn get-alloc-size + "Calculate allocation size for a sequential collection of identical + objects of the specified size." + [coll item-size] + (assert (counted? coll) "`coll` should be constant time countable") + (* item-size (count coll))) (defn alloc "Allocates an arbitrary amount of bytes (aligned to 4 bytes). @@ -61,5 +63,11 @@ (defn slice "Returns a copy of a portion of a typed array into a new typed array object selected from start to end." - [heap start end] - (.slice ^js heap start end)) + [heap offset size] + (.slice ^js heap offset (+ offset size))) + +(defn view + "Returns a new typed array on the same ArrayBuffer store and with the + same element types as for this typed array." + [heap offset size] + (.subarray ^js heap offset (+ offset size))) diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index 1d6754a939..12a8f93616 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -55,18 +55,32 @@ (catch :default _e [uuid/zero]))) -(defn heapu32-set-u32 - [value heap offset] - (aset heap offset value)) +(defn write-u32 + [offset heap value] + (assert (instance? js/Uint32Array heap) "expected Uint32Array instance for `heap`") + (aset heap offset value) + (inc offset)) -(defn heapu32-set-uuid - [id heap offset] +(defn write-f32 + [offset heap value] + (assert (instance? js/Float32Array heap) "expected Float32Array instance for `heap`") + (aset heap offset value) + (inc offset)) + +(defn write-uuid + "Write a uuid to 32 bits addressed heap and return the offset + after write." + [offset heap id] + (assert (instance? js/Uint32Array heap) "expected Uint32Array instance for `heap`") (let [buffer (uuid/get-u32 id)] (.set heap buffer offset) - buffer)) + (+ offset 4))) -(defn heapf32-set-matrix - [matrix heap offset] +(defn write-matrix + "Write a matrix to 32 bits addressed heap and return the offset + after write." + [offset heap matrix] + (assert (instance? js/Float32Array heap) "expected Float32Array instance for `heap`") (let [a (dm/get-prop matrix :a) b (dm/get-prop matrix :b) c (dm/get-prop matrix :c) @@ -78,7 +92,8 @@ (aset heap (+ offset 2) c) (aset heap (+ offset 3) d) (aset heap (+ offset 4) e) - (aset heap (+ offset 5) f))) + (aset heap (+ offset 5) f) + (+ offset 6))) (defn translate-shape-type [type] From 7fa7a806a8c71dc9e5d56e4a8fb55dee7d305203 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 08:48:06 +0200 Subject: [PATCH 02/10] :zap: Remove unnecesary allocation of corners on wasm api set-shape --- frontend/src/app/render_wasm/api.cljs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 43cd169e6b..5568b56897 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -369,12 +369,17 @@ (h/call wasm/internal-module "_set_shape_blur" type hidden value))) (defn set-shape-corners - [corners] - (let [r1 (or (get corners 0) 0) - r2 (or (get corners 1) 0) - r3 (or (get corners 2) 0) - r4 (or (get corners 3) 0)] - (h/call wasm/internal-module "_set_shape_corners" r1 r2 r3 r4))) + [shape] + (let [r1 (dm/get-prop shape :r1)] + (when (some? r1) + (let [r2 (dm/get-prop shape :r2) + r3 (dm/get-prop shape :r3) + r4 (dm/get-prop shape :r4)] + (h/call wasm/internal-module "_set_shape_corners" + (d/nilv r1 0) + (d/nilv r2 0) + (d/nilv r3 0) + (d/nilv r4 0)))))) (defn set-flex-layout [shape] @@ -685,11 +690,6 @@ bool-type (dm/get-prop shape :bool-type) grow-type (dm/get-prop shape :grow-type) blur (dm/get-prop shape :blur) - corners (when (some? (dm/get-prop shape :r1)) - [(dm/get-prop shape :r1) - (dm/get-prop shape :r2) - (dm/get-prop shape :r3) - (dm/get-prop shape :r4)]) svg-attrs (dm/get-prop shape :svg-attrs) shadows (dm/get-prop shape :shadow)] @@ -705,6 +705,7 @@ (set-shape-opacity opacity) (set-shape-hidden hidden) (set-shape-children children) + (set-shape-corners shape) (when (and (= type :group) masked) (set-masked masked)) (when (some? blur) @@ -719,7 +720,6 @@ (set-shape-path-content content)) (when (and (some? content) (= type :svg-raw)) (set-shape-svg-raw-content (get-static-markup shape))) - (when (some? corners) (set-shape-corners corners)) (when (some? shadows) (set-shape-shadows shadows)) (when (= type :text) (set-shape-grow-type grow-type)) From 9f14edb0d763dd870482ba67145753a2164ce8d3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 08:54:26 +0200 Subject: [PATCH 03/10] :zap: Remove unnecessary anonymouns fn allocation from set-flex-layout And also removes usage of dm/get-prop for props that are known to be not static --- frontend/src/app/render_wasm/api.cljs | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 5568b56897..db36d4049a 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -383,22 +383,24 @@ (defn set-flex-layout [shape] - (let [dir (-> (or (dm/get-prop shape :layout-flex-dir) :row) sr/translate-layout-flex-dir) - gap (dm/get-prop shape :layout-gap) - row-gap (or (dm/get-prop gap :row-gap) 0) - column-gap (or (dm/get-prop gap :column-gap) 0) + (let [dir (-> (get shape :layout-flex-dir :row) + (sr/translate-layout-flex-dir)) + gap (get shape :layout-gap) + row-gap (get gap :row-gap 0) + column-gap (get gap :column-gap 0) - align-items (-> (or (dm/get-prop shape :layout-align-items) :start) sr/translate-layout-align-items) - align-content (-> (or (dm/get-prop shape :layout-align-content) :stretch) sr/translate-layout-align-content) - justify-items (-> (or (dm/get-prop shape :layout-justify-items) :start) sr/translate-layout-justify-items) - justify-content (-> (or (dm/get-prop shape :layout-justify-content) :stretch) sr/translate-layout-justify-content) - wrap-type (-> (or (dm/get-prop shape :layout-wrap-type) :nowrap) sr/translate-layout-wrap-type) + align-items (-> (get shape :layout-align-items :start) sr/translate-layout-align-items) + align-content (-> (get shape :layout-align-content :stretch) sr/translate-layout-align-content) + justify-items (-> (get shape :layout-justify-items :start) sr/translate-layout-justify-items) + justify-content (-> (get shape :layout-justify-content :stretch) sr/translate-layout-justify-content) + wrap-type (-> (get shape :layout-wrap-type :nowrap) sr/translate-layout-wrap-type) + + padding (get shape :layout-padding) + padding-top (get padding :p1 0) + padding-right (get padding :p2 0) + padding-bottom (get padding :p3 0) + padding-left (get padding :p4 0)] - padding (dm/get-prop shape :layout-padding) - padding-top (or (dm/get-prop padding :p1) 0) - padding-right (or (dm/get-prop padding :p2) 0) - padding-bottom (or (dm/get-prop padding :p3) 0) - padding-left (or (dm/get-prop padding :p4) 0)] (h/call wasm/internal-module "_set_flex_layout_data" dir From 3f71734cb4425bc44ddf152d4808242550787787 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 08:57:31 +0200 Subject: [PATCH 04/10] :zap: Remove unnecessary anon fn allocation on set-grid-layout-data And remove incorrect use of dm/get prop for non statically known attributes of shape --- frontend/src/app/render_wasm/api.cljs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index db36d4049a..e2d905ec4b 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -418,21 +418,22 @@ (defn set-grid-layout-data [shape] - (let [dir (-> (or (dm/get-prop shape :layout-grid-dir) :row) sr/translate-layout-grid-dir) - gap (dm/get-prop shape :layout-gap) - row-gap (or (dm/get-prop gap :row-gap) 0) - column-gap (or (dm/get-prop gap :column-gap) 0) + (let [dir (-> (get shape :layout-grid-dir :row) + (sr/translate-layout-grid-dir)) + gap (get shape :layout-gap) + row-gap (get gap :row-gap 0) + column-gap (get gap :column-gap 0) - align-items (-> (or (dm/get-prop shape :layout-align-items) :start) sr/translate-layout-align-items) - align-content (-> (or (dm/get-prop shape :layout-align-content) :stretch) sr/translate-layout-align-content) - justify-items (-> (or (dm/get-prop shape :layout-justify-items) :start) sr/translate-layout-justify-items) - justify-content (-> (or (dm/get-prop shape :layout-justify-content) :stretch) sr/translate-layout-justify-content) + align-items (-> (get shape :layout-align-items :start) sr/translate-layout-align-items) + align-content (-> (get shape :layout-align-content :stretch) sr/translate-layout-align-content) + justify-items (-> (get shape :layout-justify-items :start) sr/translate-layout-justify-items) + justify-content (-> (get shape :layout-justify-content :stretch) sr/translate-layout-justify-content) - padding (dm/get-prop shape :layout-padding) - padding-top (or (dm/get-prop padding :p1) 0) - padding-right (or (dm/get-prop padding :p2) 0) - padding-bottom (or (dm/get-prop padding :p3) 0) - padding-left (or (dm/get-prop padding :p4) 0)] + padding (get shape :layout-padding) + padding-top (get padding :p1 0) + padding-right (get padding :p2 0) + padding-bottom (get padding :p3 0) + padding-left (get padding :p4 0)] (h/call wasm/internal-module "_set_grid_layout_data" From 129d3e61faaf5cf94255a7fbe07d0c217b37cad2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 10:30:02 +0200 Subject: [PATCH 05/10] :tada: Add missing wrap method on buffer abstraction --- common/src/app/common/buffer.cljc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/common/src/app/common/buffer.cljc b/common/src/app/common/buffer.cljc index 69413db7db..226e547b45 100644 --- a/common/src/app/common/buffer.cljc +++ b/common/src/app/common/buffer.cljc @@ -127,6 +127,18 @@ (finally (.order ~target ByteOrder/LITTLE_ENDIAN)))))) +(defn wrap + [data] + #?(:clj (let [buffer (ByteBuffer/wrap ^bytes data)] + (.order buffer ByteOrder/LITTLE_ENDIAN)) + :cljs + (cond + (instance? js/Uint8Array data) + (new js/DataView (.-buffer ^js data)) + + :else + (throw (js/Error. "unexpected type"))))) + (defn allocate [size] #?(:clj (let [buffer (ByteBuffer/allocate (int size))] From 761a0a7009f788689eb86b0e3ae4ddec41237b27 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 10:32:35 +0200 Subject: [PATCH 06/10] :sparkles: Improve memory write operations on set-grid-layout-rows --- frontend/src/app/render_wasm/api.cljs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index e2d905ec4b..575db126a7 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -8,6 +8,7 @@ "A WASM based render API" (:require ["react-dom/server" :as rds] + [app.common.buffer :as buf] [app.common.data :as d :refer [not-empty?]] [app.common.data.macros :as dm] [app.common.types.fills :as types.fills] @@ -451,18 +452,22 @@ (defn set-grid-layout-rows [entries] - (let [size (mem/get-alloc-size entries GRID-LAYOUT-ROW-U8-SIZE) - offset (mem/alloc size) - heap (-> (mem/get-heap-u8) - (mem/view offset size))] + (let [size (mem/get-alloc-size entries GRID-LAYOUT-ROW-U8-SIZE) + offset (mem/alloc size) + heapu8 (mem/get-heap-u8) + buffer (buf/wrap heapu8)] + + (reduce (fn [offset {:keys [type value]}] + ;; NOTE: because the not well alligned nature of grid + ;; row data structure, we use buffer/dataview way for + ;; write data instead of the used to way of secuentally + ;; write chaining the offset. + (buf/write-byte buffer (+ offset 0) (sr/translate-grid-track-type type)) + (buf/write-float buffer (+ offset 1) value) + (+ offset GRID-LAYOUT-ROW-U8-SIZE)) + offset + entries) - (loop [entries (seq entries) - current-offset 0] - (when-not (empty? entries) - (let [{:keys [type value]} (first entries)] - (.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ current-offset 0)) - (.set heap (sr/f32->u8 value) (+ current-offset 1)) - (recur (rest entries) (+ current-offset GRID-LAYOUT-ROW-U8-SIZE))))) (h/call wasm/internal-module "_set_grid_rows"))) (defn set-grid-layout-columns From f32b92a5b07ce701ec9b570c4b6d90c979916805 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 10:33:08 +0200 Subject: [PATCH 07/10] :sparkles: Assign defaults on serializers instead on api For make the operations more efficient --- frontend/src/app/render_wasm/api.cljs | 18 +++++++++--------- frontend/src/app/render_wasm/serializers.cljs | 15 ++++++++++----- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 575db126a7..bb60f88fd9 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -390,11 +390,11 @@ row-gap (get gap :row-gap 0) column-gap (get gap :column-gap 0) - align-items (-> (get shape :layout-align-items :start) sr/translate-layout-align-items) - align-content (-> (get shape :layout-align-content :stretch) sr/translate-layout-align-content) - justify-items (-> (get shape :layout-justify-items :start) sr/translate-layout-justify-items) - justify-content (-> (get shape :layout-justify-content :stretch) sr/translate-layout-justify-content) - wrap-type (-> (get shape :layout-wrap-type :nowrap) sr/translate-layout-wrap-type) + align-items (-> (get shape :layout-align-items) sr/translate-layout-align-items) + align-content (-> (get shape :layout-align-content) sr/translate-layout-align-content) + justify-items (-> (get shape :layout-justify-items) sr/translate-layout-justify-items) + justify-content (-> (get shape :layout-justify-content) sr/translate-layout-justify-content) + wrap-type (-> (get shape :layout-wrap-type) sr/translate-layout-wrap-type) padding (get shape :layout-padding) padding-top (get padding :p1 0) @@ -425,10 +425,10 @@ row-gap (get gap :row-gap 0) column-gap (get gap :column-gap 0) - align-items (-> (get shape :layout-align-items :start) sr/translate-layout-align-items) - align-content (-> (get shape :layout-align-content :stretch) sr/translate-layout-align-content) - justify-items (-> (get shape :layout-justify-items :start) sr/translate-layout-justify-items) - justify-content (-> (get shape :layout-justify-content :stretch) sr/translate-layout-justify-content) + align-items (-> (get shape :layout-align-items) sr/translate-layout-align-items) + align-content (-> (get shape :layout-align-content) sr/translate-layout-align-content) + justify-items (-> (get shape :layout-justify-items) sr/translate-layout-justify-items) + justify-content (-> (get shape :layout-justify-content) sr/translate-layout-justify-content) padding (get shape :layout-padding) padding-top (get padding :p1 0) diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index 12a8f93616..49a6cca78b 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -212,7 +212,8 @@ :start 0 :end 1 :center 2 - :stretch 3)) + :stretch 3 + 0)) (defn translate-layout-align-content [align-content] @@ -223,7 +224,8 @@ :space-between 3 :space-around 4 :space-evenly 5 - :stretch 6)) + :stretch 6 + 6)) (defn translate-layout-justify-items [justify-items] @@ -231,7 +233,8 @@ :start 0 :end 1 :center 2 - :stretch 3)) + :stretch 3 + 0)) (defn translate-layout-justify-content [justify-content] @@ -242,13 +245,15 @@ :space-between 3 :space-around 4 :space-evenly 5 - :stretch 6)) + :stretch 6 + 6)) (defn translate-layout-wrap-type [wrap-type] (case wrap-type :wrap 0 - :nowrap 1)) + :nowrap 1 + 1)) (defn translate-grid-track-type [type] From 6b6e80f4b8a56c9763e9a16457cfe3c2ffdb23e5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 10:33:50 +0200 Subject: [PATCH 08/10] :bug: Fix regression introduced on the set-grid-layout-cells fn Incorrect data is used for calcultate the size --- frontend/src/app/render_wasm/api.cljs | 2 +- render-wasm/src/shapes/layouts.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index bb60f88fd9..ac51772dd9 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -489,7 +489,7 @@ (defn set-grid-layout-cells [cells] (let [entries (vals cells) - size (mem/get-alloc-size entries GRID-LAYOUT-CELL-U8-SIZE) + size (mem/get-alloc-size cells GRID-LAYOUT-CELL-U8-SIZE) offset (mem/alloc size) heap (-> (mem/get-heap-u8) (mem/view offset size))] diff --git a/render-wasm/src/shapes/layouts.rs b/render-wasm/src/shapes/layouts.rs index 1019ee79d1..edb2e80548 100644 --- a/render-wasm/src/shapes/layouts.rs +++ b/render-wasm/src/shapes/layouts.rs @@ -385,6 +385,7 @@ impl GridData { } } +// FIXME: use transmute #[derive(Debug)] #[repr(C)] pub struct RawGridTrack { From f9d757bb85c4f52829ba54b9844f02877738ae44 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 10:53:02 +0200 Subject: [PATCH 09/10] :sparkles: Move several mem write helpers to mem.heap32 ns For simplify usage and make it clear the required addressing is used for that functions --- frontend/src/app/render_wasm/api.cljs | 36 ++++++------- frontend/src/app/render_wasm/mem/heap32.cljs | 51 +++++++++++++++++++ frontend/src/app/render_wasm/serializers.cljs | 40 --------------- 3 files changed, 70 insertions(+), 57 deletions(-) create mode 100644 frontend/src/app/render_wasm/mem/heap32.cljs diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index ac51772dd9..95e29e612f 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -26,6 +26,7 @@ [app.render-wasm.deserializers :as dr] [app.render-wasm.helpers :as h] [app.render-wasm.mem :as mem] + [app.render-wasm.mem.heap32 :as mem.h32] [app.render-wasm.performance :as perf] [app.render-wasm.serializers :as sr] [app.render-wasm.serializers.color :as sr-clr] @@ -156,7 +157,7 @@ size (mem/get-alloc-size children UUID-U8-SIZE) offset (mem/alloc->offset-32 size)] (reduce (fn [offset id] - (sr/write-uuid offset heap id)) + (mem.h32/write-uuid offset heap id)) offset children) @@ -458,10 +459,11 @@ buffer (buf/wrap heapu8)] (reduce (fn [offset {:keys [type value]}] - ;; NOTE: because the not well alligned nature of grid - ;; row data structure, we use buffer/dataview way for - ;; write data instead of the used to way of secuentally - ;; write chaining the offset. + ;; NOTE: because of the nature of the grid row data + ;; structure memory layout we can't use fully 32 bits + ;; alligned writes, so for heteregeneus writes we use + ;; the buffer abstraction (DataView) for perform + ;; surgical writes. (buf/write-byte buffer (+ offset 0) (sr/translate-grid-track-type type)) (buf/write-float buffer (+ offset 1) value) (+ offset GRID-LAYOUT-ROW-U8-SIZE)) @@ -804,7 +806,7 @@ offset (mem/alloc->offset-32 size)] (reduce (fn [offset id] - (sr/write-uuid offset heap id)) + (mem.h32/write-uuid offset heap id)) offset entries) @@ -823,11 +825,11 @@ (reduce (fn [offset {:keys [type parent id index value]}] (-> offset - (sr/write-u32 heapu32 (sr/translate-structure-modifier-type type)) - (sr/write-u32 heapu32 (d/nilv index 0)) - (sr/write-uuid heapu32 parent) - (sr/write-uuid heapu32 id) - (sr/write-f32 heapf32 value))) + (mem.h32/write-u32 heapu32 (sr/translate-structure-modifier-type type)) + (mem.h32/write-u32 heapu32 (d/nilv index 0)) + (mem.h32/write-uuid heapu32 parent) + (mem.h32/write-uuid heapu32 id) + (mem.h32/write-f32 heapf32 value))) offset entries) @@ -843,8 +845,8 @@ (reduce (fn [offset [id transform]] (-> offset - (sr/write-uuid heapu32 id) - (sr/write-matrix heapf32 transform))) + (mem.h32/write-uuid heapu32 id) + (mem.h32/write-matrix heapf32 transform))) offset entries) @@ -873,7 +875,7 @@ heapf32 (mem/get-heap-f32)] (reduce (fn [offset id] - (sr/write-uuid offset heapu32 id)) + (mem.h32/write-uuid offset heapu32 id)) offset entries) @@ -907,8 +909,8 @@ (reduce (fn [offset [id transform]] (-> offset - (sr/write-uuid heapu32 id) - (sr/write-matrix heapf32 transform))) + (mem.h32/write-uuid heapu32 id) + (mem.h32/write-matrix heapf32 transform))) offset modifiers) @@ -1020,7 +1022,7 @@ offset (mem/alloc->offset-32 size)] (reduce (fn [offset id] - (sr/write-uuid offset heap id)) + (mem.h32/write-uuid offset heap id)) offset (rseq ids)) diff --git a/frontend/src/app/render_wasm/mem/heap32.cljs b/frontend/src/app/render_wasm/mem/heap32.cljs new file mode 100644 index 0000000000..ed5f8137fb --- /dev/null +++ b/frontend/src/app/render_wasm/mem/heap32.cljs @@ -0,0 +1,51 @@ +;; 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.render-wasm.mem.heap32 + "A memory write helpers that uses 32 bits addressed offsets." + (:require + [app.common.data.macros :as dm] + [app.common.uuid :as uuid])) + +(defn write-u32 + [offset heap value] + (assert (instance? js/Uint32Array heap) "expected Uint32Array instance for `heap`") + (aset heap offset value) + (inc offset)) + +(defn write-f32 + [offset heap value] + (assert (instance? js/Float32Array heap) "expected Float32Array instance for `heap`") + (aset heap offset value) + (inc offset)) + +(defn write-uuid + "Write a uuid to 32 bits addressed heap and return the offset + after write." + [offset heap id] + (assert (instance? js/Uint32Array heap) "expected Uint32Array instance for `heap`") + (let [buffer (uuid/get-u32 id)] + (.set heap buffer offset) + (+ offset 4))) + +(defn write-matrix + "Write a matrix to 32 bits addressed heap and return the offset + after write." + [offset heap matrix] + (assert (instance? js/Float32Array heap) "expected Float32Array instance for `heap`") + (let [a (dm/get-prop matrix :a) + b (dm/get-prop matrix :b) + c (dm/get-prop matrix :c) + d (dm/get-prop matrix :d) + e (dm/get-prop matrix :e) + f (dm/get-prop matrix :f)] + (aset heap (+ offset 0) a) + (aset heap (+ offset 1) b) + (aset heap (+ offset 2) c) + (aset heap (+ offset 3) d) + (aset heap (+ offset 4) e) + (aset heap (+ offset 5) f) + (+ offset 6))) diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index 49a6cca78b..bf9dfb45cb 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -55,46 +55,6 @@ (catch :default _e [uuid/zero]))) -(defn write-u32 - [offset heap value] - (assert (instance? js/Uint32Array heap) "expected Uint32Array instance for `heap`") - (aset heap offset value) - (inc offset)) - -(defn write-f32 - [offset heap value] - (assert (instance? js/Float32Array heap) "expected Float32Array instance for `heap`") - (aset heap offset value) - (inc offset)) - -(defn write-uuid - "Write a uuid to 32 bits addressed heap and return the offset - after write." - [offset heap id] - (assert (instance? js/Uint32Array heap) "expected Uint32Array instance for `heap`") - (let [buffer (uuid/get-u32 id)] - (.set heap buffer offset) - (+ offset 4))) - -(defn write-matrix - "Write a matrix to 32 bits addressed heap and return the offset - after write." - [offset heap matrix] - (assert (instance? js/Float32Array heap) "expected Float32Array instance for `heap`") - (let [a (dm/get-prop matrix :a) - b (dm/get-prop matrix :b) - c (dm/get-prop matrix :c) - d (dm/get-prop matrix :d) - e (dm/get-prop matrix :e) - f (dm/get-prop matrix :f)] - (aset heap (+ offset 0) a) - (aset heap (+ offset 1) b) - (aset heap (+ offset 2) c) - (aset heap (+ offset 3) d) - (aset heap (+ offset 4) e) - (aset heap (+ offset 5) f) - (+ offset 6))) - (defn translate-shape-type [type] (case type From e69d61eaf47955a416321502145c25a4ec0729f6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 12 Aug 2025 11:27:13 +0200 Subject: [PATCH 10/10] :sparkles: Add facilities for work with dataview with common alases --- common/src/app/common/buffer.cljc | 12 ++------ frontend/src/app/render_wasm/api.cljs | 30 ++++++++++--------- frontend/src/app/render_wasm/mem.cljs | 16 ++++++++++ frontend/src/app/render_wasm/serializers.cljs | 1 - 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/common/src/app/common/buffer.cljc b/common/src/app/common/buffer.cljc index 226e547b45..55ec4f83c8 100644 --- a/common/src/app/common/buffer.cljc +++ b/common/src/app/common/buffer.cljc @@ -129,15 +129,9 @@ (defn wrap [data] - #?(:clj (let [buffer (ByteBuffer/wrap ^bytes data)] - (.order buffer ByteOrder/LITTLE_ENDIAN)) - :cljs - (cond - (instance? js/Uint8Array data) - (new js/DataView (.-buffer ^js data)) - - :else - (throw (js/Error. "unexpected type"))))) + #?(:clj (let [buffer (ByteBuffer/wrap ^bytes data)] + (.order buffer ByteOrder/LITTLE_ENDIAN)) + :cljs (new js/DataView (.-buffer ^js data)))) (defn allocate [size] diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 95e29e612f..4c4008bed7 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -8,7 +8,6 @@ "A WASM based render API" (:require ["react-dom/server" :as rds] - [app.common.buffer :as buf] [app.common.data :as d :refer [not-empty?]] [app.common.data.macros :as dm] [app.common.types.fills :as types.fills] @@ -455,8 +454,7 @@ [entries] (let [size (mem/get-alloc-size entries GRID-LAYOUT-ROW-U8-SIZE) offset (mem/alloc size) - heapu8 (mem/get-heap-u8) - buffer (buf/wrap heapu8)] + dview (mem/get-data-view)] (reduce (fn [offset {:keys [type value]}] ;; NOTE: because of the nature of the grid row data @@ -464,8 +462,8 @@ ;; alligned writes, so for heteregeneus writes we use ;; the buffer abstraction (DataView) for perform ;; surgical writes. - (buf/write-byte buffer (+ offset 0) (sr/translate-grid-track-type type)) - (buf/write-float buffer (+ offset 1) value) + (mem/write-u8 dview (+ offset 0) (sr/translate-grid-track-type type)) + (mem/write-f32 dview (+ offset 1) value) (+ offset GRID-LAYOUT-ROW-U8-SIZE)) offset entries) @@ -476,16 +474,20 @@ [entries] (let [size (mem/get-alloc-size entries GRID-LAYOUT-COLUMN-U8-SIZE) offset (mem/alloc size) - heap (-> (mem/get-heap-u8) - (mem/view offset size))] + dview (mem/get-data-view)] + + (reduce (fn [offset {:keys [type value]}] + ;; NOTE: because of the nature of the grid column data + ;; structure memory layout we can't use fully 32 bits + ;; alligned writes, so for heteregeneus writes we use + ;; the buffer abstraction (DataView) for perform + ;; surgical writes. + (mem/write-u8 dview (+ offset 0) (sr/translate-grid-track-type type)) + (mem/write-f32 dview (+ offset 1) value) + (+ offset GRID-LAYOUT-COLUMN-U8-SIZE)) + offset + entries) - (loop [entries (seq entries) - current-offset 0] - (when-not (empty? entries) - (let [{:keys [type value]} (first entries)] - (.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ current-offset 0)) - (.set heap (sr/f32->u8 value) (+ current-offset 1)) - (recur (rest entries) (+ current-offset GRID-LAYOUT-COLUMN-U8-SIZE))))) (h/call wasm/internal-module "_set_grid_columns"))) (defn set-grid-layout-cells diff --git a/frontend/src/app/render_wasm/mem.cljs b/frontend/src/app/render_wasm/mem.cljs index e56ec4a01f..59562329c8 100644 --- a/frontend/src/app/render_wasm/mem.cljs +++ b/frontend/src/app/render_wasm/mem.cljs @@ -6,6 +6,7 @@ (ns app.render-wasm.mem (:require + [app.common.buffer :as buf] [app.render-wasm.helpers :as h] [app.render-wasm.wasm :as wasm])) @@ -71,3 +72,18 @@ same element types as for this typed array." [heap offset size] (.subarray ^js heap offset (+ offset size))) + +(defn get-data-view + "Returns a heap wrapped in a DataView for surgical write operations" + [] + (buf/wrap (get-heap-u8))) + +(defn write-u8 + "Write unsigned int8. Expects a DataView instance" + [target offset value] + (buf/write-byte target offset value)) + +(defn write-f32 + "Write float32. Expects a DataView instance" + [target offset value] + (buf/write-float target offset value)) diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index bf9dfb45cb..5efd965b6d 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -6,7 +6,6 @@ (ns app.render-wasm.serializers (:require - [app.common.data.macros :as dm] [app.common.uuid :as uuid] [cuerdas.core :as str]))