mirror of
https://github.com/penpot/penpot.git
synced 2026-11-08 11:04:44 +00:00
⚡ Speed up uploading page shapes to wasm (#12854)
* ⚡ Compute text fonts once per shape when uploading to wasm * ⚡ Write the shapes batch in place and coerce fills once * 🐛 Free the upload buffer when a batch record write fails
This commit is contained in:
parent
988cde3c9d
commit
7ecf48c52c
@ -205,17 +205,29 @@
|
||||
(buf/write-i32 dview (+ offset 36) (d/nilv z-index 0))
|
||||
(+ offset 40)))
|
||||
|
||||
(defn- coerce-fills
|
||||
"Binary fills, or nil when there are none."
|
||||
[fills]
|
||||
(when-not (or (nil? fills) (and (vector? fills) (empty? fills)))
|
||||
(types.fills/coerce fills)))
|
||||
|
||||
(defn- fills-byte-size
|
||||
[fills]
|
||||
(if (some? fills) (types.fills/get-byte-size fills) 4))
|
||||
|
||||
(defn- write-fills-section!
|
||||
"Write fills in the same layout as `_set_shape_fills`:
|
||||
[u8 n][u8;3 pad][n × FILL-U8-SIZE]. Returns next offset."
|
||||
[dview offset fills]
|
||||
(let [fills (types.fills/coerce (or fills []))
|
||||
byte-size (types.fills/get-byte-size fills)
|
||||
;; write-to expects a Uint32Array heap + u32 element offset
|
||||
heap-u32 (js/Uint32Array. (.-buffer dview))
|
||||
u32-off (quot offset 4)]
|
||||
(types.fills/write-to fills heap-u32 u32-off)
|
||||
(+ offset byte-size)))
|
||||
(if (some? fills)
|
||||
;; write-to expects a Uint32Array heap + u32 element offset
|
||||
(let [heap-u32 (js/Uint32Array. (.-buffer dview)
|
||||
(.-byteOffset dview)
|
||||
(quot (.-byteLength dview) 4))]
|
||||
(types.fills/write-to fills heap-u32 (quot offset 4))
|
||||
(+ offset (types.fills/get-byte-size fills)))
|
||||
(do (buf/write-u32 dview offset 0)
|
||||
(+ offset 4))))
|
||||
|
||||
(defn- write-stroke-fill!
|
||||
[dview offset stroke]
|
||||
@ -288,15 +300,16 @@
|
||||
(defn write-shape-payload!
|
||||
"Serialize one shape's structural payload into `dview` starting at `offset`
|
||||
(payload only — no length prefix). Returns the offset after the payload.
|
||||
`fills` and `strokes` come from `prepare-record`.
|
||||
|
||||
Options:
|
||||
- `:include-layout?` — when true, emit FLEX + LAYOUT-ITEM (workspace cold load).
|
||||
- `:include-fills-strokes?` — when true, emit FILLS + STROKES sections."
|
||||
[dview offset shape {:keys [include-layout? include-fills-strokes?]
|
||||
:or {include-layout? false
|
||||
include-fills-strokes? false}}]
|
||||
[dview offset shape children fills strokes
|
||||
{:keys [include-layout? include-fills-strokes?]
|
||||
:or {include-layout? false
|
||||
include-fills-strokes? false}}]
|
||||
(let [shape-type (dm/get-prop shape :type)
|
||||
children (into [] (filter uuid?) (get shape :shapes))
|
||||
blur (get shape :blur)
|
||||
bg-blur (get shape :background-blur)
|
||||
shadows (or (get shape :shadow) [])
|
||||
@ -305,7 +318,6 @@
|
||||
grow-type (when (= shape-type :text) (get shape :grow-type))
|
||||
flex? (and include-layout? (ctl/flex-layout? shape))
|
||||
layout-item? include-layout?
|
||||
strokes (when include-fills-strokes? (visible-strokes shape))
|
||||
|
||||
mask (cond-> 0
|
||||
true (bit-or SECTION-CHILDREN)
|
||||
@ -376,7 +388,7 @@
|
||||
|
||||
offset (cond-> offset
|
||||
include-fills-strokes?
|
||||
(as-> o (write-fills-section! dview o (get shape :fills))))
|
||||
(as-> o (write-fills-section! dview o fills)))
|
||||
|
||||
offset (cond-> offset
|
||||
include-fills-strokes?
|
||||
@ -384,18 +396,14 @@
|
||||
offset))
|
||||
|
||||
(defn- payload-byte-size
|
||||
[shape {:keys [include-layout? include-fills-strokes?]
|
||||
:or {include-layout? false include-fills-strokes? false}}]
|
||||
(let [children (into [] (filter uuid?) (get shape :shapes))
|
||||
shadows (or (get shape :shadow) [])
|
||||
[shape children fills strokes {:keys [include-layout? include-fills-strokes?]
|
||||
:or {include-layout? false include-fills-strokes? false}}]
|
||||
(let [shadows (or (get shape :shadow) [])
|
||||
shape-type (dm/get-prop shape :type)
|
||||
blur (get shape :blur)
|
||||
bg-blur (get shape :background-blur)
|
||||
flex? (and include-layout? (ctl/flex-layout? shape))
|
||||
fills-size (if include-fills-strokes?
|
||||
(types.fills/get-byte-size (types.fills/coerce (or (get shape :fills) [])))
|
||||
0)
|
||||
strokes (when include-fills-strokes? (visible-strokes shape))
|
||||
fills-size (if include-fills-strokes? (fills-byte-size fills) 0)
|
||||
strokes-size (if include-fills-strokes?
|
||||
(+ 4 (* (count strokes)
|
||||
(+ STROKE-HEADER-U8-SIZE types.fills.impl/FILL-U8-SIZE)))
|
||||
@ -414,18 +422,35 @@
|
||||
fills-size
|
||||
strokes-size)))
|
||||
|
||||
(defn- encode-shape-record
|
||||
"Returns a Uint8Array: [u32 payload_len][payload]."
|
||||
[shape opts]
|
||||
(let [capacity (+ 4 (payload-byte-size shape opts))
|
||||
buffer (js/ArrayBuffer. capacity)
|
||||
dview (js/DataView. buffer)
|
||||
end (write-shape-payload! dview 4 shape opts)
|
||||
payload-len (- end 4)]
|
||||
(assert (= end capacity)
|
||||
(str "upload record size mismatch: wrote " end " expected " capacity))
|
||||
(buf/write-u32 dview 0 payload-len)
|
||||
(js/Uint8Array. buffer 0 end)))
|
||||
(defn- prepare-record
|
||||
"Everything a record needs, computed once: children, binary fills, visible
|
||||
strokes and payload size."
|
||||
[shape {:keys [include-fills-strokes?] :as opts}]
|
||||
(let [children (into [] (filter uuid?) (get shape :shapes))
|
||||
fills (when include-fills-strokes? (coerce-fills (get shape :fills)))
|
||||
strokes (when include-fills-strokes? (visible-strokes shape))]
|
||||
#js {:shape shape
|
||||
:children children
|
||||
:fills fills
|
||||
:strokes strokes
|
||||
:size (payload-byte-size shape children fills strokes opts)}))
|
||||
|
||||
(defn- write-record!
|
||||
"Writes [u32 payload_len][payload] at `offset`. Returns the next offset."
|
||||
[dview offset ^js record opts]
|
||||
(let [size (.-size record)
|
||||
start (+ offset 4)
|
||||
end (write-shape-payload! dview start
|
||||
(.-shape record)
|
||||
(.-children record)
|
||||
(.-fills record)
|
||||
(.-strokes record)
|
||||
opts)]
|
||||
(when (not= end (+ start size))
|
||||
(throw (ex-info "upload record size mismatch"
|
||||
{:wrote (- end start) :expected size})))
|
||||
(buf/write-u32 dview offset size)
|
||||
end))
|
||||
|
||||
(defn flush-shapes-batch!
|
||||
"Upload `shapes` as one `_set_shapes_batch` call.
|
||||
@ -433,17 +458,20 @@
|
||||
`:include-fills-strokes?`)."
|
||||
[shapes opts]
|
||||
(when (and (wasm/live?) (seq shapes))
|
||||
(let [records (mapv #(encode-shape-record % opts) shapes)
|
||||
total (reduce (fn [acc ^js u8] (+ acc (.-byteLength u8))) 4 records)
|
||||
(let [records (mapv #(prepare-record % opts) shapes)
|
||||
total (reduce (fn [acc ^js record] (+ acc 4 (.-size record))) 4 records)
|
||||
offset (mem/alloc total)
|
||||
heap (mem/get-heap-u8)
|
||||
dview (js/DataView. (.-buffer heap))]
|
||||
(buf/write-u32 dview offset (count records))
|
||||
(reduce (fn [o ^js u8]
|
||||
(.set heap u8 o)
|
||||
(+ o (.-byteLength u8)))
|
||||
(+ offset 4)
|
||||
records)
|
||||
dview (js/DataView. (.-buffer heap) offset total)]
|
||||
(try
|
||||
(.fill heap 0 offset (+ offset total))
|
||||
(buf/write-u32 dview 0 (count records))
|
||||
(reduce (fn [o record] (write-record! dview o record opts))
|
||||
4
|
||||
records)
|
||||
(catch :default cause
|
||||
(mem/free)
|
||||
(throw cause)))
|
||||
(h/call wasm/internal-module "_set_shapes_batch")
|
||||
nil)))
|
||||
|
||||
|
||||
@ -742,9 +742,14 @@
|
||||
(let [text (apply str (map :text spans))]
|
||||
(t/write-shape-text spans paragraph text)))))
|
||||
|
||||
(defn set-shape-text-content
|
||||
"This function sets shape text content and returns a stream that loads the needed fonts asynchronously"
|
||||
[shape-id content]
|
||||
(defn- text-content-fonts
|
||||
"Content fonts and fallback faces (emoji, Noto, ...) of a text content."
|
||||
[content]
|
||||
{:fonts (f/get-content-fonts content)
|
||||
:fallback-fonts (fonts-from-text-content content false)})
|
||||
|
||||
(defn- upload-text-content
|
||||
[shape-id content {:keys [fonts fallback-fonts]}]
|
||||
|
||||
;; Cache content for text editor sync
|
||||
(text-editor/cache-shape-text-content! shape-id content)
|
||||
@ -758,15 +763,17 @@
|
||||
|
||||
(set-shape-vertical-align (get content :vertical-align))
|
||||
|
||||
(let [fonts (f/get-content-fonts content)
|
||||
fallback-fonts (fonts-from-text-content content false)
|
||||
all-fonts (concat fonts fallback-fonts)
|
||||
result (f/store-fonts all-fonts)]
|
||||
(let [result (f/store-fonts (concat fonts fallback-fonts))]
|
||||
(write-text-content! content)
|
||||
(f/load-fallback-fonts-for-editor! fallback-fonts)
|
||||
(h/call wasm/internal-module "_update_shape_text_layout")
|
||||
result)))
|
||||
|
||||
(defn set-shape-text-content
|
||||
"This function sets shape text content and returns a stream that loads the needed fonts asynchronously"
|
||||
[shape-id content]
|
||||
(upload-text-content shape-id content (text-content-fonts content)))
|
||||
|
||||
(defn apply-styles-to-selection
|
||||
"Apply style attrs to the currently selected text spans.
|
||||
Updates the cached content, pushes to WASM, and returns {:shape-id :content} for saving.
|
||||
@ -1523,16 +1530,10 @@
|
||||
(def ^:private empty-text-font-state
|
||||
{:font-index {} :pending-faces #{}})
|
||||
|
||||
(defn- text-layout-fonts
|
||||
"Content and fallback faces (emoji, Noto, ...) that `set-shape-text-content`
|
||||
uploads, must match that path for pending-face tracking."
|
||||
[content]
|
||||
(into #{} (concat (f/get-content-fonts content)
|
||||
(fonts-from-text-content content false))))
|
||||
|
||||
(defn- text-font-face-keys-state
|
||||
"All font-face keys for a text content, and the subset not WASM-ready yet."
|
||||
[content]
|
||||
"All font-face keys for the `text-content-fonts` of a text, and the subset
|
||||
not WASM-ready yet."
|
||||
[{:keys [fonts fallback-fonts]}]
|
||||
(reduce
|
||||
(fn [acc font]
|
||||
(let [font-data (f/make-font-data font)
|
||||
@ -1543,7 +1544,7 @@
|
||||
(cond-> pending?
|
||||
(update :pending-font-face-keys conj key)))))
|
||||
{:font-face-keys #{} :pending-font-face-keys #{}}
|
||||
(text-layout-fonts content)))
|
||||
(into #{} (concat fonts fallback-fonts))))
|
||||
|
||||
(defn- acc-text-font-state
|
||||
[{:keys [font-index pending-faces]} id font-face-keys pending-font-face-keys]
|
||||
@ -1560,7 +1561,7 @@
|
||||
(if (cfh/text-shape? shape)
|
||||
(let [content (ensure-text-content (:content shape))
|
||||
{:keys [font-face-keys pending-font-face-keys]}
|
||||
(text-font-face-keys-state content)]
|
||||
(text-font-face-keys-state (text-content-fonts content))]
|
||||
(acc-text-font-state empty-text-font-state
|
||||
(:id shape)
|
||||
font-face-keys
|
||||
@ -1613,9 +1614,10 @@
|
||||
(set-layout-data shape)))
|
||||
|
||||
(let [is-text? (= type :text)
|
||||
text-fonts (when is-text? (text-content-fonts content))
|
||||
{:keys [font-face-keys pending-font-face-keys]}
|
||||
(when is-text? (text-font-face-keys-state content))
|
||||
text-content-pending (when is-text? (set-shape-text-content id content))
|
||||
(when is-text? (text-font-face-keys-state text-fonts))
|
||||
text-content-pending (when is-text? (upload-text-content id content text-fonts))
|
||||
pending-thumbnails (into [] (concat
|
||||
text-content-pending
|
||||
(when is-text? (set-shape-text-images id content true))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user