mirror of
https://github.com/penpot/penpot.git
synced 2026-07-31 18:36:18 +00:00
🐛 Fix wasm context restore guards (#10824)
* 🐛 Prevent WASM panics during WebGL context restore Delay context-restored until reload finishes and no-op app mutations while reloading so resize/modifiers cannot panic mid-teardown. * 🐛 Fall back to CLJS bool content when WASM is not ready Returning nil from calculate-bool would persist empty path content during context loss/reload; use path/calc-bool-content instead.
This commit is contained in:
parent
e14e7bb616
commit
4cd34b22e2
@ -20,7 +20,6 @@
|
||||
[app.main.worker :as mw]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.render-wasm.shape :as wasm.shape]
|
||||
[app.render-wasm.wasm :as wasm]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
@ -90,8 +89,7 @@
|
||||
(ptk/reify ::sync-wasm-structural-changes
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(when (and wasm/context-initialized?
|
||||
(not @wasm/context-lost?))
|
||||
(when (wasm.api/initialized?)
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
shapes
|
||||
(into []
|
||||
|
||||
@ -355,8 +355,7 @@
|
||||
features (features/get-enabled-features state team-id)
|
||||
render-wasm-enabled? (features/active-feature? state "render-wasm/v1")
|
||||
render-wasm-ready? #(and render-wasm-enabled?
|
||||
wasm-state/context-initialized?
|
||||
(not @wasm-state/context-lost?))]
|
||||
(wasm-state/ready?))]
|
||||
|
||||
(log/debug :hint "initialize-workspace"
|
||||
:team-id (dm/str team-id)
|
||||
|
||||
@ -683,7 +683,7 @@
|
||||
ty (.-f first-matrix)]
|
||||
(if-let [base @cache]
|
||||
(translate-selrect base tx ty)
|
||||
(let [computed (wasm.api/get-selection-rect ids)]
|
||||
(when-let [computed (wasm.api/get-selection-rect ids)]
|
||||
(vreset! cache (translate-selrect computed (- tx) (- ty)))
|
||||
computed))))
|
||||
|
||||
@ -729,15 +729,27 @@
|
||||
(vreset! wasm-structure-modifiers-active? true)))
|
||||
(let [geometry-entries (parse-geometry-modifiers modif-tree)
|
||||
root-modifiers (into [] (map (fn [[id data]] [id (:transform data)])) geometry-entries)
|
||||
wasm-ready? (wasm.api/initialized?)
|
||||
;; While the GL context is down (lost / mid-reload), keep the
|
||||
;; root transforms so SVG selection/preview can still move.
|
||||
;; `propagate-modifiers` returns [] when not ready, do not
|
||||
;; treat that as "no modifiers".
|
||||
modifiers
|
||||
(if (and translation? (not snap-pixel?))
|
||||
(cond
|
||||
(or (not wasm-ready?)
|
||||
(and translation? (not snap-pixel?)))
|
||||
root-modifiers
|
||||
(wasm.api/propagate-modifiers geometry-entries snap-pixel?))]
|
||||
(wasm.api/set-modifiers modifiers)
|
||||
|
||||
:else
|
||||
(let [propagated (wasm.api/propagate-modifiers geometry-entries snap-pixel?)]
|
||||
(if (seq propagated) propagated root-modifiers)))]
|
||||
(when wasm-ready?
|
||||
(wasm.api/set-modifiers modifiers))
|
||||
(let [ids (into [] xf:map-key geometry-entries)
|
||||
selrect (if (and translation? (not snap-pixel?) selection-rect-cache (seq modifiers))
|
||||
(cached-translation-selrect ids (second (first modifiers)) selection-rect-cache)
|
||||
(wasm.api/get-selection-rect ids))]
|
||||
selrect (when wasm-ready?
|
||||
(if (and translation? (not snap-pixel?) selection-rect-cache (seq modifiers))
|
||||
(cached-translation-selrect ids (second (first modifiers)) selection-rect-cache)
|
||||
(wasm.api/get-selection-rect ids)))]
|
||||
(rx/of (set-temporary-selrect selrect)
|
||||
(set-temporary-modifiers modifiers))))))))
|
||||
|
||||
@ -795,7 +807,8 @@
|
||||
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
|
||||
|
||||
transforms
|
||||
(if (and translation? (not snap-pixel?))
|
||||
(cond
|
||||
(and translation? (not snap-pixel?))
|
||||
;; Mirror WASM `propagate_modifiers` in CLJS: splat the
|
||||
;; translation matrix onto every descendant. Without
|
||||
;; this step the commit would only touch the dragged
|
||||
@ -815,6 +828,26 @@
|
||||
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
|
||||
{}
|
||||
geometry-entries)
|
||||
|
||||
;; Context lost / mid-reload: do not call into WASM. Use
|
||||
;; root transforms (and splat translation onto descendants
|
||||
;; when we can) so the commit still lands in file data.
|
||||
(not (wasm.api/initialized?))
|
||||
(if translation?
|
||||
(reduce
|
||||
(fn [acc [id data]]
|
||||
(let [t (:transform data)
|
||||
subtree-ids
|
||||
(or (get subtree-ids-by-id id)
|
||||
(cfh/get-children-ids-with-self objects id))]
|
||||
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
|
||||
{}
|
||||
geometry-entries)
|
||||
(into {}
|
||||
(map (fn [[id data]] [id (:transform data)]))
|
||||
geometry-entries))
|
||||
|
||||
:else
|
||||
(into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?)))
|
||||
|
||||
ignore-tree
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -119,7 +119,7 @@
|
||||
|
||||
(defn update-text-layout
|
||||
[id]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/live?)
|
||||
(let [shape-id-buffer (uuid/get-u32 id)]
|
||||
(h/call wasm/internal-module "_update_shape_text_layout_for"
|
||||
(aget shape-id-buffer 0)
|
||||
@ -129,7 +129,7 @@
|
||||
|
||||
(defn force-update-text-layout
|
||||
[id]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/live?)
|
||||
(let [shape-id-buffer (uuid/get-u32 id)]
|
||||
(h/call wasm/internal-module "_force_update_shape_text_layout_for"
|
||||
(aget shape-id-buffer 0)
|
||||
@ -140,7 +140,7 @@
|
||||
;; IMPORTANT: Only TTF fonts can be stored.
|
||||
(defn- store-font-buffer
|
||||
[font-data font-array-buffer emoji? fallback?]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/live?)
|
||||
(let [font-id-buffer (:family-id-buffer font-data)
|
||||
size (.-byteLength font-array-buffer)
|
||||
ptr (h/call wasm/internal-module "_alloc_bytes" size)
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
|
||||
Returns nil."
|
||||
[shape]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/live?)
|
||||
(let [id (dm/get-prop shape :id)
|
||||
parent-id (get shape :parent-id)
|
||||
shape-type (dm/get-prop shape :type)
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
(defn get-webgl-context
|
||||
"Gets the WebGL context from the WASM module"
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/live?)
|
||||
(let [gl-obj (unchecked-get wasm/internal-module "GL")]
|
||||
(when gl-obj
|
||||
;; Get the current WebGL context from Emscripten
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.render-wasm.api :as api]
|
||||
[app.render-wasm.svg-filters :as svg-filters]
|
||||
[app.render-wasm.wasm :as wasm]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.core :as c]
|
||||
[cuerdas.core :as str]))
|
||||
@ -129,7 +128,7 @@
|
||||
;; The `set-wasm-attr!` can return a list of callbacks to be executed in a second pass.
|
||||
(defn- set-wasm-attr!
|
||||
[shape k]
|
||||
(when wasm/context-initialized?
|
||||
(when (api/initialized?)
|
||||
(let [shape (case k
|
||||
:svg-attrs (svg-filters/apply-svg-derived (assoc shape :svg-attrs (get shape :svg-attrs)))
|
||||
(:fills :blur :shadow) (svg-filters/apply-svg-derived shape)
|
||||
@ -334,14 +333,15 @@
|
||||
|
||||
(defn process-shape-changes!
|
||||
[objects shape-changes]
|
||||
(let [shape-changes
|
||||
(->> shape-changes
|
||||
;; We don't need to update the model for shapes not in the current page
|
||||
(filter (fn [[shape-id _]] (shape-in-current-page? shape-id))))]
|
||||
(when (d/not-empty? shape-changes)
|
||||
(->> (rx/from shape-changes)
|
||||
(rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props)))
|
||||
(rx/subs! #(api/request-render "set-wasm-attrs"))))))
|
||||
(when (api/initialized?)
|
||||
(let [shape-changes
|
||||
(->> shape-changes
|
||||
;; We don't need to update the model for shapes not in the current page
|
||||
(filter (fn [[shape-id _]] (shape-in-current-page? shape-id))))]
|
||||
(when (d/not-empty? shape-changes)
|
||||
(->> (rx/from shape-changes)
|
||||
(rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props)))
|
||||
(rx/subs! #(api/request-render "set-wasm-attrs")))))))
|
||||
|
||||
;; `conj` empty set initialization
|
||||
(def conj* (fnil conj (d/ordered-set)))
|
||||
|
||||
@ -190,7 +190,7 @@
|
||||
|
||||
(defn text-editor-focus
|
||||
[id]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(let [buffer (uuid/get-u32 id)]
|
||||
(when-not (h/call wasm/internal-module "_text_editor_focus"
|
||||
(aget buffer 0)
|
||||
@ -202,44 +202,44 @@
|
||||
(defn text-editor-set-cursor-from-offset
|
||||
"Sets caret position from shape relative coordinates"
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_set_cursor_from_offset" x y)))
|
||||
|
||||
(defn text-editor-set-cursor-from-point
|
||||
"Sets caret position from screen (canvas) coordinates"
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_set_cursor_from_point" x y)))
|
||||
|
||||
(defn text-editor-toggle-overtype-mode
|
||||
"Toggles overtype mode"
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_toggle_overtype_mode")))
|
||||
|
||||
(defn text-editor-pointer-down
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_pointer_down" x y)))
|
||||
|
||||
(defn text-editor-pointer-move
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_pointer_move" x y)))
|
||||
|
||||
(defn text-editor-pointer-up
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_pointer_up" x y)))
|
||||
|
||||
(defn text-editor-update-blink
|
||||
[timestamp-ms]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_update_blink" timestamp-ms)))
|
||||
|
||||
(defn text-editor-render-overlay
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_render_overlay")))
|
||||
|
||||
(defn text-editor-render-caret
|
||||
@ -252,7 +252,7 @@
|
||||
|
||||
(defn text-editor-poll-event
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(let [res (h/call wasm/internal-module "_text_editor_poll_event")]
|
||||
res)))
|
||||
|
||||
@ -323,7 +323,7 @@
|
||||
|
||||
(defn text-editor-get-current-styles
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(let [ptr (h/call wasm/internal-module "_text_editor_get_current_styles")]
|
||||
(when (and ptr (not (zero? ptr)))
|
||||
(let [heap-u8 (mem/get-heap-u8)
|
||||
@ -411,7 +411,7 @@
|
||||
(defn text-editor-encode-text-pre
|
||||
[text]
|
||||
(when (and (not (empty? text))
|
||||
wasm/context-initialized?)
|
||||
(wasm/ready?))
|
||||
(let [encoder (js/TextEncoder.)
|
||||
buf (.encode encoder text)
|
||||
heapu8 (mem/get-heap-u8)
|
||||
@ -422,31 +422,31 @@
|
||||
(defn text-editor-encode-text-post
|
||||
[text]
|
||||
(when (and (not (empty? text))
|
||||
wasm/context-initialized?)
|
||||
(wasm/ready?))
|
||||
(mem/free)))
|
||||
|
||||
(defn text-editor-composition-start
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_composition_start")))
|
||||
|
||||
(defn text-editor-composition-update
|
||||
[text]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(text-editor-encode-text-pre text)
|
||||
(h/call wasm/internal-module "_text_editor_composition_update")
|
||||
(text-editor-encode-text-post text)))
|
||||
|
||||
(defn text-editor-composition-end
|
||||
[text]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(text-editor-encode-text-pre text)
|
||||
(h/call wasm/internal-module "_text_editor_composition_end")
|
||||
(text-editor-encode-text-post text)))
|
||||
|
||||
(defn text-editor-insert-text
|
||||
[text]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(text-editor-encode-text-pre text)
|
||||
(h/call wasm/internal-module "_text_editor_insert_text")
|
||||
(text-editor-encode-text-post text)))
|
||||
@ -455,62 +455,62 @@
|
||||
([]
|
||||
(text-editor-delete-backward false))
|
||||
([word-boundary]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_delete_backward" word-boundary))))
|
||||
|
||||
(defn text-editor-delete-forward
|
||||
([]
|
||||
(text-editor-delete-forward false))
|
||||
([word-boundary]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_delete_forward" word-boundary))))
|
||||
|
||||
(defn text-editor-insert-paragraph []
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_insert_paragraph")))
|
||||
|
||||
(defn text-editor-move-cursor
|
||||
[direction word-boundary extend-selection]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_move_cursor" direction word-boundary (if extend-selection 1 0))))
|
||||
|
||||
(defn text-editor-select-all
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_select_all")))
|
||||
|
||||
(defn text-editor-select-word-boundary
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_select_word_boundary" x y)))
|
||||
|
||||
(defn text-editor-blur
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(when-not (h/call wasm/internal-module "_text_editor_blur")
|
||||
(throw (js/Error. "TextEditor blur failed")))))
|
||||
|
||||
(defn text-editor-dispose
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(h/call wasm/internal-module "_text_editor_dispose")))
|
||||
|
||||
(defn text-editor-has-focus?
|
||||
([id]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(not (zero? (h/call wasm/internal-module "_text_editor_has_focus_with_id" id)))))
|
||||
([]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(not (zero? (h/call wasm/internal-module "_text_editor_has_focus"))))))
|
||||
|
||||
(defn text-editor-has-selection?
|
||||
([]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(not (zero? (h/call wasm/internal-module "_text_editor_has_selection"))))))
|
||||
|
||||
(defn text-editor-export-content
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(let [ptr (h/call wasm/internal-module "_text_editor_export_content")]
|
||||
(when (and ptr (not (zero? ptr)))
|
||||
(let [json-str (mem/read-string ptr)]
|
||||
@ -520,7 +520,7 @@
|
||||
(defn text-editor-export-selection
|
||||
"Export only the currently selected text as plain text from the WASM editor. Requires WASM support (_text_editor_export_selection)."
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(let [ptr (h/call wasm/internal-module "_text_editor_export_selection")]
|
||||
(when (and ptr (not (zero? ptr)))
|
||||
(let [text (mem/read-string ptr)]
|
||||
@ -529,7 +529,7 @@
|
||||
|
||||
(defn text-editor-get-active-shape-id
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(try
|
||||
(let [byte-offset (mem/alloc 16)
|
||||
u32-offset (mem/->offset-32 byte-offset)
|
||||
@ -549,7 +549,7 @@
|
||||
|
||||
(defn text-editor-get-selection
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(let [byte-offset (mem/alloc 16)
|
||||
u32-offset (mem/->offset-32 byte-offset)
|
||||
heap (mem/get-heap-u32)
|
||||
@ -643,7 +643,7 @@
|
||||
shape-id and the fully merged content map ready for
|
||||
v2-update-text-shape-content."
|
||||
[]
|
||||
(when (and wasm/context-initialized? (text-editor-has-focus?))
|
||||
(when (and (wasm/ready?) (text-editor-has-focus?))
|
||||
(let [shape-id (text-editor-get-active-shape-id)
|
||||
new-texts (text-editor-export-content)]
|
||||
(when (and shape-id new-texts)
|
||||
@ -707,7 +707,7 @@
|
||||
|
||||
(defn apply-styles-to-selection
|
||||
[attrs use-shape-fn set-shape-text-content-fn]
|
||||
(when wasm/context-initialized?
|
||||
(when (wasm/ready?)
|
||||
(let [shape-id (text-editor-get-active-shape-id)
|
||||
selection (text-editor-get-selection)]
|
||||
|
||||
|
||||
@ -28,6 +28,11 @@
|
||||
(defonce context-initialized? false)
|
||||
(defonce context-lost? (atom false))
|
||||
|
||||
;; True while `reload-renderer!` is reconstructing the GL/WASM pipeline after a
|
||||
;; WebGL context restore (or an explicit reload). External app callers must not
|
||||
;; touch WASM until this clears (`ready?`); reload-internal ops use `live?`.
|
||||
(defonce reloading? (atom false))
|
||||
|
||||
;; When we're rendering in a sync way we want to stop the asynchrous `request-render`
|
||||
(defonce disable-request-render? (atom false))
|
||||
|
||||
@ -35,15 +40,30 @@
|
||||
[]
|
||||
(and internal-module (fn? (unchecked-get internal-module "_init"))))
|
||||
|
||||
(defn live?
|
||||
"GL/WASM context exists and is not marked lost. True during reload after re-init."
|
||||
[]
|
||||
(and context-initialized? (not @context-lost?)))
|
||||
|
||||
(defn ready?
|
||||
"Safe for normal application WASM calls (not lost, not mid-reload)."
|
||||
[]
|
||||
(and (live?) (not @reloading?)))
|
||||
|
||||
(defn reset-context-state!
|
||||
"Clears canvas/GL handles and marks the context uninitialized.
|
||||
|
||||
Intentionally does **not** clear `context-lost?` or `reloading?`. During a
|
||||
context-restore reload, `clear-canvas` runs while recovery is still in
|
||||
progress; clearing those flags here would reopen a window where callers think
|
||||
WebGL is ready before re-init finishes."
|
||||
[]
|
||||
(set! internal-frame-id nil)
|
||||
(set! canvas nil)
|
||||
(set! canvas-snapshot nil)
|
||||
(set! gl-context-handle nil)
|
||||
(set! gl-context nil)
|
||||
(set! context-initialized? false)
|
||||
(reset! context-lost? false))
|
||||
(set! context-initialized? false))
|
||||
|
||||
(defonce serializers
|
||||
#js {:raster-format shared/RasterFormat
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user