🐛 Fix premature WASM view-interaction end during pan (#10425)

This commit is contained in:
Alejandro Alonso 2026-06-25 15:07:37 +02:00 committed by GitHub
parent 67386a0358
commit 345affc687
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 39 additions and 13 deletions

View File

@ -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!)))

View File

@ -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

View File

@ -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(())

View File

@ -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) {