mirror of
https://github.com/penpot/penpot.git
synced 2026-06-28 02:02:06 +00:00
🐛 Fix premature WASM view-interaction end during pan (#10425)
This commit is contained in:
parent
67386a0358
commit
345affc687
@ -27,4 +27,4 @@
|
||||
(defn maybe-view-interaction-end!
|
||||
[state]
|
||||
(when (and (features/active-feature? state "render-wasm/v1") (not (render-context-lost? state)))
|
||||
(wasm.api/view-interaction-end!)))
|
||||
(wasm.api/finalize-view-interaction!)))
|
||||
@ -357,12 +357,15 @@
|
||||
(def ^:const FRAME_TYPE_NONE 0) ;; This type should never "leak".
|
||||
(def ^:const FRAME_TYPE_PARTIAL 1) ;; A frame needs more render calls to end.
|
||||
(def ^:const FRAME_TYPE_FULL 2) ;; A frame was full.
|
||||
(def ^:const RENDER-FLAG-SYNC-TILES 4) ;; Rebuild tile index without ending fast mode (pan/zoom pause).
|
||||
|
||||
(defn- internal-render
|
||||
([]
|
||||
(internal-render 0))
|
||||
([timestamp]
|
||||
(set! wasm/internal-frame-type (h/call wasm/internal-module "_render" timestamp wasm/internal-frame-type))
|
||||
(internal-render timestamp wasm/internal-frame-type))
|
||||
([timestamp flags]
|
||||
(set! wasm/internal-frame-type (h/call wasm/internal-module "_render" timestamp flags))
|
||||
(when (= wasm/internal-frame-type FRAME_TYPE_PARTIAL)
|
||||
(request-render "frame-type-partial"))))
|
||||
|
||||
@ -1311,20 +1314,27 @@
|
||||
(perf/end-measure "render-finish")
|
||||
(reset! view-interaction-active? false)))
|
||||
|
||||
(defn- view-gesture-active?
|
||||
"True while a pointer-driven pan or zoom gesture is in progress."
|
||||
[]
|
||||
(let [local (get @st/state :workspace-local)]
|
||||
(or (:panning local) (:zooming local))))
|
||||
|
||||
(defn finalize-view-interaction!
|
||||
"Ends the view interaction and triggers a full-quality render."
|
||||
[]
|
||||
(view-interaction-end!)
|
||||
(internal-render 0 0))
|
||||
|
||||
(def render-finish
|
||||
(letfn [(do-render []
|
||||
;; Check if context is still initialized before executing
|
||||
;; to prevent errors when navigating quickly
|
||||
(when (initialized?)
|
||||
(view-interaction-end!)
|
||||
;; Use async _render: visible tiles render synchronously
|
||||
;; (no yield), interest-area tiles render progressively
|
||||
;; via rAF. _set_view_end already rebuilt the tile
|
||||
;; index. For pan, most tiles are cached so the render
|
||||
;; completes in the first frame. For zoom, interest-
|
||||
;; area tiles (~3 tile margin) don't block the main
|
||||
;; thread.
|
||||
(internal-render)))]
|
||||
(if (view-gesture-active?)
|
||||
;; Pan/zoom pause: render without ending the interaction.
|
||||
(internal-render 0 RENDER-FLAG-SYNC-TILES)
|
||||
(finalize-view-interaction!))))]
|
||||
(fns/debounce do-render DEBOUNCE_DELAY_MS)))
|
||||
|
||||
(defn set-view-box
|
||||
|
||||
@ -123,6 +123,9 @@ pub extern "C" fn render(timestamp: i32, flags: u8) -> Result<FrameType> {
|
||||
}
|
||||
}
|
||||
let is_partial = flags & RenderFlag::Partial as u8 == RenderFlag::Partial as u8;
|
||||
if flags & RenderFlag::SyncTiles as u8 != 0 {
|
||||
render_state.preserve_target_during_render = true;
|
||||
}
|
||||
let frame_type = if is_partial && !render_state.preserve_target_during_render {
|
||||
state
|
||||
.continue_render_loop(timestamp)
|
||||
@ -355,6 +358,10 @@ pub extern "C" fn set_view_end() -> Result<()> {
|
||||
// instead of re-drawing every visible tile from scratch.
|
||||
render_state.rebuild_tile_index(&state.shapes);
|
||||
}
|
||||
// Avoid `reset_canvas` on the post-gesture render (pan at stable zoom).
|
||||
if !render_state.options.is_profile_rebuild_tiles() {
|
||||
render_state.preserve_target_during_render = true;
|
||||
}
|
||||
performance::end_measure!("set_view_end");
|
||||
});
|
||||
Ok(())
|
||||
|
||||
@ -54,7 +54,8 @@ pub enum FrameType {
|
||||
pub enum RenderFlag {
|
||||
None = 0,
|
||||
Partial = 1,
|
||||
Full = 2,
|
||||
/// Rebuilds the tile index without leaving fast mode.
|
||||
SyncTiles = 4,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -2092,6 +2093,10 @@ impl RenderState {
|
||||
let preserve_target = self.preserve_target_during_render;
|
||||
self.preserve_target_during_render = false;
|
||||
|
||||
if preserve_target && self.options.is_fast_mode() {
|
||||
self.rebuild_tile_index(tree);
|
||||
}
|
||||
|
||||
if self.options.is_interactive_transform() {
|
||||
// Keep `Target` as the previous frame and overwrite only the tiles
|
||||
// that changed. This avoids clearing + redrawing an atlas backdrop
|
||||
@ -3899,7 +3904,11 @@ impl RenderState {
|
||||
let mut all_tiles = HashSet::<tiles::Tile>::new();
|
||||
|
||||
let ids = std::mem::take(&mut self.touched_ids);
|
||||
self.preserve_target_during_render = !ids.is_empty();
|
||||
// Pan release sets `preserve_target` in `set_view_end`; don't reset it
|
||||
// here when no shapes changed, or the next render clears the canvas.
|
||||
if !ids.is_empty() {
|
||||
self.preserve_target_during_render = true;
|
||||
}
|
||||
|
||||
for shape_id in ids.iter() {
|
||||
if let Some(shape) = tree.get(shape_id) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user