Changes to modifiers

This commit is contained in:
alonso.torres
2025-10-24 11:42:53 +02:00
parent 59e745e9ab
commit ed4df73e42
9 changed files with 214 additions and 139 deletions

View File

@@ -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;
@@ -537,6 +538,7 @@ pub extern "C" fn set_structure_modifiers() {
.collect();
with_state_mut!(state, {
let mut structure = HashMap::new();
for entry in entries {
match entry.entry_type {
StructureEntryType::ScaleContent => {
@@ -548,15 +550,17 @@ pub extern "C" fn set_structure_modifiers() {
}
}
_ => {
state.structure.entry(entry.parent).or_insert_with(Vec::new);
state
.structure
structure.entry(entry.parent).or_insert_with(Vec::new);
structure
.get_mut(&entry.parent)
.expect("Parent not found for entry")
.push(entry);
}
}
}
if !structure.is_empty() {
state.shapes.set_structure(structure);
}
});
mem::free_bytes();
@@ -567,7 +571,8 @@ pub extern "C" fn clean_modifiers() {
with_state_mut!(state, {
state.structure.clear();
state.scale_content.clear();
state.modifiers.clear();
// state.modifiers.clear();
state.shapes.clean_modifiers();
});
}
@@ -595,11 +600,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::<Uuid>::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);
});
}

View File

@@ -1141,13 +1141,8 @@ impl RenderState {
self.get_rect_bounds(rect)
}
pub fn get_shape_extrect_bounds(
&mut self,
shape: &Shape,
tree: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) -> 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)
}
@@ -1285,7 +1280,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 {
@@ -1304,18 +1299,14 @@ impl RenderState {
let transformed_element: Cow<Shape> = 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));
}
@@ -1664,23 +1655,14 @@ impl RenderState {
Ok(())
}
pub fn get_tiles_for_shape(
&mut self,
shape: &Shape,
tree: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) -> 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<Uuid, Matrix>,
) {
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<tiles::Tile> = self
.tiles
.get_tiles_of(shape.id)
@@ -1724,7 +1706,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);
@@ -1754,7 +1736,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);
@@ -1778,15 +1760,11 @@ impl RenderState {
&mut self,
shape_ids: &IndexSet<Uuid>,
tree: &mut ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) {
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);
}
}
}
@@ -1798,14 +1776,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<Uuid, Matrix>,
) {
let ids: Vec<_> = modifiers.keys().collect();
pub fn rebuild_modifier_tiles(&mut self, tree: &mut ShapesPool, ids: Vec<Uuid>) {
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 {

View File

@@ -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<Uuid> {
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<Uuid, Matrix>,
) -> 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<Uuid, Matrix>,
) -> 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<Uuid, Matrix>,
) -> 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<Shape> = 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<Uuid, Matrix>,
) -> 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
}
@@ -1179,11 +1153,41 @@ impl Shape {
}
}
pub fn transformed(&self, transform: Option<&Matrix>) -> Self {
pub fn apply_structure(&mut self, structure: &Vec<StructureEntry>) {
let mut result: Vec<Uuid> = Vec::from_iter(self.children.iter().copied());
let mut to_remove = HashSet::<&Uuid>::new();
for st in structure {
match st.entry_type {
StructureEntryType::AddChild => {
result.insert(result.len() - st.index as usize, st.id);
}
StructureEntryType::RemoveChild => {
to_remove.insert(&st.id);
}
_ => {}
}
}
self.children = result
.iter()
.filter(|id| !to_remove.contains(id))
.copied()
.collect();
}
pub fn transformed(
&self,
transform: Option<&Matrix>,
structure: Option<&Vec<StructureEntry>>) -> Self
{
let mut shape: Cow<Shape> = Cow::Borrowed(self);
if let Some(transform) = transform {
shape.to_mut().apply_transform(transform);
}
if let Some(structure) = structure {
shape.to_mut().apply_structure(structure);
}
shape.into_owned()
}

View File

@@ -186,11 +186,10 @@ impl ToPath for Shape {
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Path {
let shape = self.transformed(modifiers.get(&self.id));
match shape.shape_type {
match &self.shape_type {
Type::Frame(ref frame) => {
let children = shape.modified_children_ids(structure.get(&shape.id), true);
let mut result = Path::new(rect_segments(&shape, frame.corners));
let children = self.modified_children_ids(structure.get(&self.id), true);
let mut result = Path::new(rect_segments(&self, frame.corners));
for id in children {
let Some(shape) = shapes.get(&id) else {
continue;
@@ -201,7 +200,7 @@ impl ToPath for Shape {
}
Type::Group(_) => {
let children = shape.modified_children_ids(structure.get(&shape.id), true);
let children = self.modified_children_ids(structure.get(&self.id), true);
let mut result = Path::default();
for id in children {
let Some(shape) = shapes.get(&id) else {
@@ -215,13 +214,13 @@ impl ToPath for Shape {
Path::new(segments)
}
Type::Bool(bool_data) => bool_data.path,
Type::Bool(bool_data) => bool_data.path.clone(),
Type::Rect(ref rect) => Path::new(rect_segments(&shape, rect.corners)),
Type::Rect(ref rect) => Path::new(rect_segments(&self, rect.corners)),
Type::Path(path_data) => path_data,
Type::Path(path_data) => path_data.clone(),
Type::Circle => Path::new(circle_segments(&shape)),
Type::Circle => Path::new(circle_segments(&self)),
Type::SVGRaw(_) => Path::default(),
@@ -232,7 +231,7 @@ impl ToPath for Shape {
result = join_paths(result, Path::from_skia_path(path));
}
Path::new(transform_segments(result.segments().clone(), &shape))
Path::new(transform_segments(result.segments().clone(), &self))
}
}
}

View File

@@ -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<Uuid>) {
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<Uuid, skia::Matrix>) {
self.shapes.set_modifiers(modifiers);
}
}

View File

@@ -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<Shape>,
shapes_uuid_to_idx: HashMap<Uuid, usize>,
counter: usize,
shapes_uuid_to_idx: HashMap<Uuid, usize>,
modified_shape_cache: HashMap<Uuid, OnceCell<Shape>>,
modifiers: HashMap<Uuid, skia::Matrix>,
structure: HashMap<Uuid, Vec<StructureEntry>>,
}
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,22 @@ 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),
self.structure.get(id)
)
}))
} else {
let shape = &self.shapes[idx];
Some(shape)
}
} else {
Some(&self.shapes[idx])
}
}
#[allow(dead_code)]
@@ -87,4 +116,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<Uuid, skia::Matrix>) {
// 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<Uuid, Vec<StructureEntry>>) {
// 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();
}
}