From 0916b1d0ad8a0783ddc3ad0e885cdc900432ddca Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 24 Oct 2025 11:42:53 +0200 Subject: [PATCH] :construction: WIP - changes modifiers --- render-wasm/src/main.rs | 14 ++++-- render-wasm/src/render.rs | 59 +++++++---------------- render-wasm/src/shapes.rs | 50 +++++--------------- render-wasm/src/state.rs | 16 ++++--- render-wasm/src/state/shapes_pool.rs | 70 +++++++++++++++++++++++++++- 5 files changed, 115 insertions(+), 94 deletions(-) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index d80ae3b0ea..647c0e8879 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -20,6 +20,7 @@ use mem::SerializableResult; use shapes::{StructureEntry, StructureEntryType, TransformEntry}; use skia_safe as skia; use state::State; +use std::collections::HashMap; use utils::uuid_from_u32_quartet; use uuid::Uuid; @@ -595,11 +596,16 @@ pub extern "C" fn set_modifiers() { .map(|data| TransformEntry::from_bytes(data.try_into().unwrap())) .collect(); + let mut modifiers = HashMap::new(); + let mut ids = Vec::::new(); + for entry in entries { + modifiers.insert(entry.id, entry.transform); + ids.push(entry.id); + } + with_state_mut!(state, { - for entry in entries { - state.modifiers.insert(entry.id, entry.transform); - } - state.rebuild_modifier_tiles(); + state.set_modifiers(modifiers); + state.rebuild_modifier_tiles(ids); }); } diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 9bc640e013..4dfed1939b 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1134,13 +1134,8 @@ impl RenderState { self.get_rect_bounds(rect) } - pub fn get_shape_extrect_bounds( - &mut self, - shape: &Shape, - tree: &ShapesPool, - modifiers: &HashMap, - ) -> Rect { - let rect = shape.extrect(tree, modifiers); + pub fn get_shape_extrect_bounds(&mut self, shape: &Shape, tree: &ShapesPool) -> Rect { + let rect = shape.extrect(tree); self.get_rect_bounds(rect) } @@ -1278,7 +1273,7 @@ impl RenderState { // If the shape is not in the tile set, then we update // it. if self.tiles.get_tiles_of(node_id).is_none() { - self.update_tile_for(element, tree, modifiers); + self.update_tile_for(element, tree); } if visited_children { @@ -1297,18 +1292,14 @@ impl RenderState { let transformed_element: Cow = Cow::Borrowed(element); let is_visible = transformed_element - .extrect(tree, modifiers) + .extrect(tree) .intersects(self.render_area) && !transformed_element.hidden - && !transformed_element.visually_insignificant( - self.get_scale(), - tree, - modifiers, - ); + && !transformed_element.visually_insignificant(self.get_scale(), tree); if self.options.is_debug_visible() { let shape_extrect_bounds = - self.get_shape_extrect_bounds(&transformed_element, tree, modifiers); + self.get_shape_extrect_bounds(&transformed_element, tree); debug::render_debug_shape(self, None, Some(shape_extrect_bounds)); } @@ -1657,23 +1648,14 @@ impl RenderState { Ok(()) } - pub fn get_tiles_for_shape( - &mut self, - shape: &Shape, - tree: &ShapesPool, - modifiers: &HashMap, - ) -> TileRect { + pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: &ShapesPool) -> TileRect { + let extrect = shape.extrect(tree); let tile_size = tiles::get_tile_size(self.get_scale()); - tiles::get_tiles_for_rect(shape.extrect(tree, modifiers), tile_size) + tiles::get_tiles_for_rect(extrect, tile_size) } - pub fn update_tile_for( - &mut self, - shape: &Shape, - tree: &ShapesPool, - modifiers: &HashMap, - ) { - let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree, modifiers); + pub fn update_tile_for(&mut self, shape: &Shape, tree: &ShapesPool) { + let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree); let old_tiles: HashSet = self .tiles .get_tiles_of(shape.id) @@ -1717,7 +1699,7 @@ impl RenderState { if let Some(modifier) = modifiers.get(&shape_id) { shape.to_mut().apply_transform(modifier); } - self.update_tile_for(&shape, tree, modifiers); + self.update_tile_for(&shape, tree); } else { // We only need to rebuild tiles from the first level. let children = shape.modified_children_ids(structure.get(&shape.id), false); @@ -1747,7 +1729,7 @@ impl RenderState { if let Some(modifier) = modifiers.get(&shape_id) { shape.to_mut().apply_transform(modifier); } - self.update_tile_for(&shape, tree, modifiers); + self.update_tile_for(&shape, tree); } let children = shape.modified_children_ids(structure.get(&shape.id), false); @@ -1771,15 +1753,11 @@ impl RenderState { &mut self, shape_ids: &IndexSet, tree: &mut ShapesPool, - modifiers: &HashMap, ) { for shape_id in shape_ids { - if let Some(shape) = tree.get_mut(shape_id) { - shape.invalidate_extrect(); - } if let Some(shape) = tree.get(shape_id) { if !shape.id.is_nil() { - self.update_tile_for(shape, tree, modifiers); + self.update_tile_for(shape, tree); } } } @@ -1791,14 +1769,9 @@ impl RenderState { /// Additionally, it processes all ancestors of modified shapes to ensure their /// extended rectangles are properly recalculated and their tiles are updated. /// This is crucial for frames and groups that contain transformed children. - pub fn rebuild_modifier_tiles( - &mut self, - tree: &mut ShapesPool, - modifiers: &HashMap, - ) { - let ids: Vec<_> = modifiers.keys().collect(); + pub fn rebuild_modifier_tiles(&mut self, tree: &mut ShapesPool, ids: Vec) { let ancestors = all_with_ancestors(&ids, tree, false); - self.invalidate_and_update_tiles(&ancestors, tree, modifiers); + self.invalidate_and_update_tiles(&ancestors, tree); } pub fn get_scale(&self) -> f32 { diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index ff19a6c94f..43d2ba400f 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -3,7 +3,7 @@ use skia_safe::{self as skia}; use crate::uuid::Uuid; use std::borrow::Cow; use std::cell::OnceCell; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::iter::once; mod blend; @@ -196,11 +196,11 @@ pub struct Shape { // # Returns // A set of ancestor UUIDs in traversal order (closest ancestor first) pub fn all_with_ancestors( - shapes: &[&Uuid], + shapes: &[Uuid], shapes_pool: &ShapesPool, include_hidden: bool, ) -> IndexSet { - let mut pending = Vec::from(shapes); + let mut pending = Vec::from_iter(shapes.iter()); let mut result = IndexSet::new(); while !pending.is_empty() { @@ -677,13 +677,8 @@ impl Shape { self.selrect.width() } - pub fn visually_insignificant( - &self, - scale: f32, - shapes_pool: &ShapesPool, - modifiers: &HashMap, - ) -> bool { - let extrect = self.extrect(shapes_pool, modifiers); + pub fn visually_insignificant(&self, scale: f32, shapes_pool: &ShapesPool) -> bool { + let extrect = self.extrect(shapes_pool); extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE } @@ -726,14 +721,10 @@ impl Shape { self.selrect } - pub fn extrect( - &self, - shapes_pool: &ShapesPool, - modifiers: &HashMap, - ) -> math::Rect { + pub fn extrect(&self, shapes_pool: &ShapesPool) -> math::Rect { *self .extrect - .get_or_init(|| self.calculate_extrect(shapes_pool, modifiers)) + .get_or_init(|| self.calculate_extrect(shapes_pool)) } pub fn get_text_content(&self) -> &TextContent { @@ -843,12 +834,7 @@ impl Shape { rect } - fn apply_children_bounds( - &self, - mut rect: math::Rect, - shapes_pool: &ShapesPool, - modifiers: &HashMap, - ) -> math::Rect { + fn apply_children_bounds(&self, mut rect: math::Rect, shapes_pool: &ShapesPool) -> math::Rect { let include_children = match self.shape_type { Type::Group(_) => true, Type::Frame(_) => !self.clip_content, @@ -858,15 +844,7 @@ impl Shape { if include_children { for child_id in self.children_ids(false) { if let Some(child_shape) = shapes_pool.get(&child_id) { - // Create a copy of the child shape to apply any transformations - let mut transformed_element: Cow = Cow::Borrowed(child_shape); - if let Some(modifier) = modifiers.get(&child_id) { - transformed_element.to_mut().apply_transform(modifier); - } - - // Get the child's extended rectangle and join it with the container's rectangle - let child_extrect = transformed_element.extrect(shapes_pool, modifiers); - rect.join(child_extrect); + rect.join(child_shape.extrect(shapes_pool)); } } } @@ -874,12 +852,8 @@ impl Shape { rect } - pub fn calculate_extrect( - &self, - shapes_pool: &ShapesPool, - modifiers: &HashMap, - ) -> math::Rect { - let shape = self.transformed(modifiers.get(&self.id)); + pub fn calculate_extrect(&self, shapes_pool: &ShapesPool) -> math::Rect { + let shape = self; let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open()); let mut rect = match &shape.shape_type { @@ -902,7 +876,7 @@ impl Shape { rect = self.apply_stroke_bounds(rect, max_stroke); rect = self.apply_shadow_bounds(rect); rect = self.apply_blur_bounds(rect); - rect = self.apply_children_bounds(rect, shapes_pool, modifiers); + rect = self.apply_children_bounds(rect, shapes_pool); rect } diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 62ed73053d..a5d7d62b4c 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -114,8 +114,7 @@ impl State { // We don't really do a self.shapes.remove so that redo/undo keep working if let Some(shape) = self.shapes.get(&id) { let tiles::TileRect(rsx, rsy, rex, rey) = - self.render_state - .get_tiles_for_shape(shape, &self.shapes, &self.modifiers); + self.render_state.get_tiles_for_shape(shape, &self.shapes); for x in rsx..=rex { for y in rsy..=rey { let tile = tiles::Tile(x, y); @@ -159,8 +158,7 @@ impl State { pub fn update_tile_for_shape(&mut self, shape_id: Uuid) { if let Some(shape) = self.shapes.get(&shape_id) { - self.render_state - .update_tile_for(shape, &self.shapes, &self.modifiers); + self.render_state.update_tile_for(shape, &self.shapes); } } @@ -170,7 +168,7 @@ impl State { }; if !shape.id.is_nil() { self.render_state - .update_tile_for(&shape.clone(), &self.shapes, &self.modifiers); + .update_tile_for(&shape.clone(), &self.shapes); } } @@ -184,9 +182,9 @@ impl State { .rebuild_tiles(&self.shapes, &self.modifiers, &self.structure); } - pub fn rebuild_modifier_tiles(&mut self) { + pub fn rebuild_modifier_tiles(&mut self, ids: Vec) { self.render_state - .rebuild_modifier_tiles(&mut self.shapes, &self.modifiers); + .rebuild_modifier_tiles(&mut self.shapes, ids); } pub fn font_collection(&self) -> &FontCollection { @@ -217,4 +215,8 @@ impl State { None } + + pub fn set_modifiers(&mut self, modifiers: HashMap) { + self.shapes.set_modifiers(modifiers); + } } diff --git a/render-wasm/src/state/shapes_pool.rs b/render-wasm/src/state/shapes_pool.rs index e349594742..0765d638ca 100644 --- a/render-wasm/src/state/shapes_pool.rs +++ b/render-wasm/src/state/shapes_pool.rs @@ -5,6 +5,11 @@ use crate::performance; use crate::shapes::Shape; use crate::uuid::Uuid; +use crate::shapes::StructureEntry; +use crate::skia; + +use std::cell::OnceCell; + const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3; /// A pool allocator for `Shape` objects that attempts to minimize memory reallocations. @@ -20,8 +25,13 @@ const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3; /// pub struct ShapesPool { shapes: Vec, - shapes_uuid_to_idx: HashMap, counter: usize, + + shapes_uuid_to_idx: HashMap, + + modified_shape_cache: HashMap>, + modifiers: HashMap, + structure: HashMap>, } impl ShapesPool { @@ -30,6 +40,10 @@ impl ShapesPool { shapes: vec![], counter: 0, shapes_uuid_to_idx: HashMap::default(), + + modified_shape_cache: HashMap::default(), + modifiers: HashMap::default(), + structure: HashMap::default(), } } @@ -76,7 +90,19 @@ impl ShapesPool { pub fn get(&self, id: &Uuid) -> Option<&Shape> { let idx = *self.shapes_uuid_to_idx.get(id)?; - Some(&self.shapes[idx]) + if self.modifiers.contains_key(id) || self.structure.contains_key(id) { + if let Some(cell) = self.modified_shape_cache.get(id) { + Some(cell.get_or_init(|| { + let shape = &self.shapes[idx]; + shape.transformed(self.modifiers.get(id)) + })) + } else { + let shape = &self.shapes[idx]; + Some(shape) + } + } else { + Some(&self.shapes[idx]) + } } #[allow(dead_code)] @@ -87,4 +113,44 @@ impl ShapesPool { pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> { self.shapes.iter_mut() } + + #[allow(dead_code)] + fn clean_shape_cache(&mut self) { + self.modified_shape_cache.clear() + } + + #[allow(dead_code)] + pub fn set_modifiers(&mut self, modifiers: HashMap) { + self.clean_shape_cache(); + + // Initialize the cache cells because + // later we don't want to have the mutable pointer + for key in modifiers.keys() { + self.modified_shape_cache.insert(*key, OnceCell::new()); + } + self.modifiers = modifiers; + } + + #[allow(dead_code)] + pub fn set_structure(&mut self, structure: HashMap>) { + self.clean_shape_cache(); + // Initialize the cache cells because + // later we don't want to have the mutable pointer + for key in structure.keys() { + self.modified_shape_cache.insert(*key, OnceCell::new()); + } + self.structure = structure; + } + + #[allow(dead_code)] + pub fn clean_modifiers(&mut self) { + self.clean_shape_cache(); + self.modifiers = HashMap::default(); + } + + #[allow(dead_code)] + pub fn clean_structure(&mut self) { + self.clean_shape_cache(); + self.structure = HashMap::default(); + } }