mirror of
https://github.com/penpot/penpot.git
synced 2026-05-06 00:28:43 +00:00
🔧 Split the tile rebuild into a chunked async loop
This commit is contained in:
parent
c76985abee
commit
2b16f9631e
@ -946,15 +946,26 @@
|
||||
(:y position))]
|
||||
(= result 1)))
|
||||
|
||||
(defn- tile-rebuild-loop
|
||||
"Process tile rebuild in chunks via requestAnimationFrame, then render."
|
||||
[]
|
||||
(js/requestAnimationFrame
|
||||
(fn [ts]
|
||||
(when wasm/context-initialized?
|
||||
(let [has-more (h/call wasm/internal-module "_tile_rebuild_step" ts)]
|
||||
(if (pos? has-more)
|
||||
(tile-rebuild-loop)
|
||||
(render ts)))))))
|
||||
|
||||
(def render-finish
|
||||
(letfn [(do-render [ts]
|
||||
(letfn [(do-render [_ts]
|
||||
;; Check if context is still initialized before executing
|
||||
;; to prevent errors when navigating quickly
|
||||
(when wasm/context-initialized?
|
||||
(perf/begin-measure "render-finish")
|
||||
(h/call wasm/internal-module "_set_view_end")
|
||||
(render ts)
|
||||
(perf/end-measure "render-finish")))]
|
||||
(h/call wasm/internal-module "_set_view_end_async")
|
||||
(perf/end-measure "render-finish")
|
||||
(tile-rebuild-loop)))]
|
||||
(fns/debounce do-render DEBOUNCE_DELAY_MS)))
|
||||
|
||||
(def render-pan
|
||||
|
||||
@ -346,6 +346,50 @@ pub extern "C" fn set_view_end() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Like set_view_end but uses chunked tile rebuild to avoid blocking
|
||||
/// the main thread. Prepares the view state and starts the async
|
||||
/// tile rebuild process. Call `tile_rebuild_step` in a rAF loop after this.
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn set_view_end_async() -> Result<()> {
|
||||
with_state_mut!(state, {
|
||||
performance::begin_measure!("set_view_end_async");
|
||||
state.render_state.options.set_fast_mode(false);
|
||||
state.render_state.cancel_animation_frame();
|
||||
|
||||
let scale = state.render_state.get_scale();
|
||||
state
|
||||
.render_state
|
||||
.tile_viewbox
|
||||
.update(state.render_state.viewbox, scale);
|
||||
|
||||
if state.render_state.options.is_profile_rebuild_tiles() {
|
||||
// Profile mode still uses sync rebuild
|
||||
state.rebuild_tiles();
|
||||
} else {
|
||||
state.start_tile_rebuild();
|
||||
}
|
||||
|
||||
state.render_state.sync_cached_viewbox();
|
||||
performance::end_measure!("set_view_end_async");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a chunk of the tile rebuild. Returns 1 if more work remains, 0 if done.
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn tile_rebuild_step(timestamp: i32) -> Result<i32> {
|
||||
let result = with_state_mut!(state, {
|
||||
if state.process_tile_rebuild_step(timestamp) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
});
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[wasm_error]
|
||||
pub extern "C" fn clear_focus_mode() -> Result<()> {
|
||||
|
||||
@ -300,6 +300,10 @@ pub(crate) struct RenderState {
|
||||
pub ignore_nested_blurs: bool,
|
||||
/// Preview render mode - when true, uses simplified rendering for progressive loading
|
||||
pub preview_mode: bool,
|
||||
/// State for chunked tile rebuild across animation frames
|
||||
tile_rebuild_pending_nodes: Vec<Uuid>,
|
||||
tile_rebuild_zoom_changed: bool,
|
||||
tile_rebuild_in_progress: bool,
|
||||
}
|
||||
|
||||
pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
|
||||
@ -372,6 +376,9 @@ impl RenderState {
|
||||
touched_ids: HashSet::default(),
|
||||
ignore_nested_blurs: false,
|
||||
preview_mode: false,
|
||||
tile_rebuild_pending_nodes: vec![],
|
||||
tile_rebuild_zoom_changed: false,
|
||||
tile_rebuild_in_progress: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2541,6 +2548,60 @@ impl RenderState {
|
||||
performance::end_measure!("rebuild_tiles_shallow");
|
||||
}
|
||||
|
||||
/// Initialize a chunked tile rebuild that can be spread across multiple
|
||||
/// animation frames to avoid blocking the main thread.
|
||||
pub fn start_tile_rebuild(&mut self, tree: ShapesPoolRef) {
|
||||
self.tile_rebuild_zoom_changed = self.zoom_changed();
|
||||
|
||||
// Collect top-level shape ids (children of the root node)
|
||||
let mut nodes = Vec::new();
|
||||
if let Some(root) = tree.get(&Uuid::nil()) {
|
||||
for child_id in root.children_ids_iter(false) {
|
||||
nodes.push(*child_id);
|
||||
}
|
||||
}
|
||||
|
||||
self.tile_rebuild_pending_nodes = nodes;
|
||||
self.tile_rebuild_in_progress = !self.tile_rebuild_pending_nodes.is_empty();
|
||||
}
|
||||
|
||||
/// Process a batch of shapes for tile rebuild within a time budget.
|
||||
/// Returns true if there is more work remaining.
|
||||
pub fn process_tile_rebuild_step(&mut self, tree: ShapesPoolRef, timestamp: i32) -> bool {
|
||||
if !self.tile_rebuild_in_progress {
|
||||
return false;
|
||||
}
|
||||
|
||||
performance::begin_measure!("process_tile_rebuild_step");
|
||||
|
||||
while let Some(shape_id) = self.tile_rebuild_pending_nodes.pop() {
|
||||
if let Some(shape) = tree.get(&shape_id) {
|
||||
if self.tile_rebuild_zoom_changed {
|
||||
let _ = self.update_shape_tiles(shape, tree);
|
||||
} else {
|
||||
let _ = self.update_shape_tiles_incremental(shape, tree);
|
||||
}
|
||||
}
|
||||
|
||||
// Check time budget every few shapes
|
||||
if performance::get_time() - timestamp > MAX_BLOCKING_TIME_MS {
|
||||
performance::end_measure!("process_tile_rebuild_step");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// All shapes processed — finalize
|
||||
self.tile_rebuild_in_progress = false;
|
||||
self.surfaces.remove_cached_tiles(self.background_color);
|
||||
|
||||
performance::end_measure!("process_tile_rebuild_step");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_tile_rebuild_in_progress(&self) -> bool {
|
||||
self.tile_rebuild_in_progress
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles_from(&mut self, tree: ShapesPoolRef, base_id: Option<&Uuid>) {
|
||||
performance::begin_measure!("rebuild_tiles");
|
||||
|
||||
|
||||
@ -211,6 +211,15 @@ impl State {
|
||||
self.render_state.rebuild_tiles_shallow(&self.shapes);
|
||||
}
|
||||
|
||||
pub fn start_tile_rebuild(&mut self) {
|
||||
self.render_state.start_tile_rebuild(&self.shapes);
|
||||
}
|
||||
|
||||
pub fn process_tile_rebuild_step(&mut self, timestamp: i32) -> bool {
|
||||
self.render_state
|
||||
.process_tile_rebuild_step(&self.shapes, timestamp)
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles(&mut self) {
|
||||
self.render_state.rebuild_tiles_from(&self.shapes, None);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user