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

@@ -18,7 +18,6 @@
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.features :as features] [app.main.features :as features]
[app.main.worker :as mw] [app.main.worker :as mw]
[app.render-wasm.api :as wasm.api]
[app.render-wasm.shape :as wasm.shape] [app.render-wasm.shape :as wasm.shape]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
@@ -113,15 +112,7 @@
(update-in state [:files file-id :data] apply-changes))] (update-in state [:files file-id :data] apply-changes))]
(let [objects (dm/get-in state [:files file-id :data :pages-index (:current-page-id state) :objects])] (let [objects (dm/get-in state [:files file-id :data :pages-index (:current-page-id state) :objects])]
(run! (wasm.shape/process-shape-changes! objects @shape-changes))
(fn [[shape-id props]]
(wasm.api/use-shape shape-id)
(let [shape (get objects shape-id)]
(run! (partial wasm.shape/set-shape-wasm-attr! shape) props)))
@shape-changes))
(wasm.api/update-shape-tiles)
(wasm.api/request-render "set-wasm-attrs")
state) state)

View File

@@ -226,22 +226,26 @@
wasm-props wasm-props
(concat clean-props wasm-props) (concat clean-props wasm-props)
wasm-props ;; Stores a map shape -> set of properties changed
;; this is the standard format used by process-shape-changes
shape-changes
(-> (group-by first wasm-props) (-> (group-by first wasm-props)
(update-vals #(map second %)))] (update-vals #(into #{} (map (comp :property second)) %)))
;; Props are grouped by id and then assoc to the shape the new value ;; Create a new objects only with the temporary modifications
(run! (fn [[id properties]] objects-changed
(let [shape (->> wasm-props
(->> properties (reduce
(reduce (fn [objects [id properties]]
(fn [shape {:keys [property value]}] (let [shape
(assoc shape property value)) (->> properties
(get objects id)))] (reduce
(fn [shape {:keys [property value]}]
;; With the new values to the shape change multi props (assoc shape property value))
(wasm.shape/set-wasm-multi-attrs! shape (->> properties (map :property))))) (get objects id)))]
wasm-props))) (assoc objects id shape)))
objects))]
(wasm.shape/process-shape-changes! objects-changed shape-changes)))
(defn clear-local-transform [] (defn clear-local-transform []
(ptk/reify ::clear-local-transform (ptk/reify ::clear-local-transform

View File

@@ -6,12 +6,14 @@
(ns app.render-wasm.shape (ns app.render-wasm.shape
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.transit :as t] [app.common.transit :as t]
[app.common.types.shape :as shape] [app.common.types.shape :as shape]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.render-wasm.api :as api] [app.render-wasm.api :as api]
[beicon.v2.core :as rx]
[cljs.core :as c] [cljs.core :as c]
[cuerdas.core :as str])) [cuerdas.core :as str]))
@@ -118,8 +120,11 @@
(-write writer (str "#penpot/shape " (:id delegate))))) (-write writer (str "#penpot/shape " (:id delegate)))))
;; --- SHAPE IMPL ;; --- SHAPE IMPL
;; When an attribute is sent to WASM it could still be pending some side operations
(defn set-shape-wasm-attr! ;; for example: font loading when changing a text, this is an async operation that will
;; resolve eventually.
;; The `set-wasm-attr!` can return a list of callbacks to be executed in a second pass.
(defn- set-wasm-attr!
[shape k] [shape k]
(let [v (get shape k) (let [v (get shape k)
id (get shape :id)] id (get shape :id)]
@@ -224,14 +229,32 @@
(ctl/flex-layout? shape) (ctl/flex-layout? shape)
(api/set-flex-layout shape))) (api/set-flex-layout shape)))
;; Property not in WASM
nil))) nil)))
(defn set-wasm-multi-attrs! (defn process-shape!
[shape properties] [shape properties]
(let [shape-id (dm/get-prop shape :id)] (let [shape-id (dm/get-prop shape :id)]
(when (shape-in-current-page? shape-id) (if (shape-in-current-page? shape-id)
(api/use-shape shape-id) (do
(run! (partial set-shape-wasm-attr! shape) properties)))) (api/use-shape shape-id)
(->> properties
(mapcat #(set-wasm-attr! shape %))
(d/index-by :key :callback)
(vals)
(rx/from)
(rx/mapcat (fn [callback] (callback)))
(rx/reduce conj [])))
(rx/empty))))
(defn process-shape-changes!
[objects shape-changes]
(->> (rx/from shape-changes)
(rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props)))
(rx/subs!
(fn [_]
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs")))))
(defn- impl-assoc (defn- impl-assoc
[self k v] [self k v]

View File

@@ -20,6 +20,7 @@ use mem::SerializableResult;
use shapes::{StructureEntry, StructureEntryType, TransformEntry}; use shapes::{StructureEntry, StructureEntryType, TransformEntry};
use skia_safe as skia; use skia_safe as skia;
use state::State; use state::State;
use std::collections::HashMap;
use utils::uuid_from_u32_quartet; use utils::uuid_from_u32_quartet;
use uuid::Uuid; use uuid::Uuid;
@@ -537,6 +538,7 @@ pub extern "C" fn set_structure_modifiers() {
.collect(); .collect();
with_state_mut!(state, { with_state_mut!(state, {
let mut structure = HashMap::new();
for entry in entries { for entry in entries {
match entry.entry_type { match entry.entry_type {
StructureEntryType::ScaleContent => { StructureEntryType::ScaleContent => {
@@ -548,15 +550,17 @@ pub extern "C" fn set_structure_modifiers() {
} }
} }
_ => { _ => {
state.structure.entry(entry.parent).or_insert_with(Vec::new); structure.entry(entry.parent).or_insert_with(Vec::new);
state structure
.structure
.get_mut(&entry.parent) .get_mut(&entry.parent)
.expect("Parent not found for entry") .expect("Parent not found for entry")
.push(entry); .push(entry);
} }
} }
} }
if !structure.is_empty() {
state.shapes.set_structure(structure);
}
}); });
mem::free_bytes(); mem::free_bytes();
@@ -567,7 +571,8 @@ pub extern "C" fn clean_modifiers() {
with_state_mut!(state, { with_state_mut!(state, {
state.structure.clear(); state.structure.clear();
state.scale_content.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())) .map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
.collect(); .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, { with_state_mut!(state, {
for entry in entries { state.set_modifiers(modifiers);
state.modifiers.insert(entry.id, entry.transform); state.rebuild_modifier_tiles(ids);
}
state.rebuild_modifier_tiles();
}); });
} }

View File

@@ -1141,13 +1141,8 @@ impl RenderState {
self.get_rect_bounds(rect) self.get_rect_bounds(rect)
} }
pub fn get_shape_extrect_bounds( pub fn get_shape_extrect_bounds(&mut self, shape: &Shape, tree: &ShapesPool) -> Rect {
&mut self, let rect = shape.extrect(tree);
shape: &Shape,
tree: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) -> Rect {
let rect = shape.extrect(tree, modifiers);
self.get_rect_bounds(rect) self.get_rect_bounds(rect)
} }
@@ -1285,7 +1280,7 @@ impl RenderState {
// If the shape is not in the tile set, then we update // If the shape is not in the tile set, then we update
// it. // it.
if self.tiles.get_tiles_of(node_id).is_none() { 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 { if visited_children {
@@ -1304,18 +1299,14 @@ impl RenderState {
let transformed_element: Cow<Shape> = Cow::Borrowed(element); let transformed_element: Cow<Shape> = Cow::Borrowed(element);
let is_visible = transformed_element let is_visible = transformed_element
.extrect(tree, modifiers) .extrect(tree)
.intersects(self.render_area) .intersects(self.render_area)
&& !transformed_element.hidden && !transformed_element.hidden
&& !transformed_element.visually_insignificant( && !transformed_element.visually_insignificant(self.get_scale(), tree);
self.get_scale(),
tree,
modifiers,
);
if self.options.is_debug_visible() { if self.options.is_debug_visible() {
let shape_extrect_bounds = 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)); debug::render_debug_shape(self, None, Some(shape_extrect_bounds));
} }
@@ -1664,23 +1655,14 @@ impl RenderState {
Ok(()) Ok(())
} }
pub fn get_tiles_for_shape( pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: &ShapesPool) -> TileRect {
&mut self, let extrect = shape.extrect(tree);
shape: &Shape,
tree: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) -> TileRect {
let tile_size = tiles::get_tile_size(self.get_scale()); 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( pub fn update_tile_for(&mut self, shape: &Shape, tree: &ShapesPool) {
&mut self, let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
shape: &Shape,
tree: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) {
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree, modifiers);
let old_tiles: HashSet<tiles::Tile> = self let old_tiles: HashSet<tiles::Tile> = self
.tiles .tiles
.get_tiles_of(shape.id) .get_tiles_of(shape.id)
@@ -1724,7 +1706,7 @@ impl RenderState {
if let Some(modifier) = modifiers.get(&shape_id) { if let Some(modifier) = modifiers.get(&shape_id) {
shape.to_mut().apply_transform(modifier); shape.to_mut().apply_transform(modifier);
} }
self.update_tile_for(&shape, tree, modifiers); self.update_tile_for(&shape, tree);
} else { } else {
// We only need to rebuild tiles from the first level. // We only need to rebuild tiles from the first level.
let children = shape.modified_children_ids(structure.get(&shape.id), false); 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) { if let Some(modifier) = modifiers.get(&shape_id) {
shape.to_mut().apply_transform(modifier); 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); let children = shape.modified_children_ids(structure.get(&shape.id), false);
@@ -1778,15 +1760,11 @@ impl RenderState {
&mut self, &mut self,
shape_ids: &IndexSet<Uuid>, shape_ids: &IndexSet<Uuid>,
tree: &mut ShapesPool, tree: &mut ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) { ) {
for shape_id in shape_ids { 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 let Some(shape) = tree.get(shape_id) {
if !shape.id.is_nil() { 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 /// Additionally, it processes all ancestors of modified shapes to ensure their
/// extended rectangles are properly recalculated and their tiles are updated. /// extended rectangles are properly recalculated and their tiles are updated.
/// This is crucial for frames and groups that contain transformed children. /// This is crucial for frames and groups that contain transformed children.
pub fn rebuild_modifier_tiles( pub fn rebuild_modifier_tiles(&mut self, tree: &mut ShapesPool, ids: Vec<Uuid>) {
&mut self,
tree: &mut ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) {
let ids: Vec<_> = modifiers.keys().collect();
let ancestors = all_with_ancestors(&ids, tree, false); 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 { pub fn get_scale(&self) -> f32 {

View File

@@ -3,7 +3,7 @@ use skia_safe::{self as skia};
use crate::uuid::Uuid; use crate::uuid::Uuid;
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::OnceCell; use std::cell::OnceCell;
use std::collections::{HashMap, HashSet}; use std::collections::HashSet;
use std::iter::once; use std::iter::once;
mod blend; mod blend;
@@ -196,11 +196,11 @@ pub struct Shape {
// # Returns // # Returns
// A set of ancestor UUIDs in traversal order (closest ancestor first) // A set of ancestor UUIDs in traversal order (closest ancestor first)
pub fn all_with_ancestors( pub fn all_with_ancestors(
shapes: &[&Uuid], shapes: &[Uuid],
shapes_pool: &ShapesPool, shapes_pool: &ShapesPool,
include_hidden: bool, include_hidden: bool,
) -> IndexSet<Uuid> { ) -> IndexSet<Uuid> {
let mut pending = Vec::from(shapes); let mut pending = Vec::from_iter(shapes.iter());
let mut result = IndexSet::new(); let mut result = IndexSet::new();
while !pending.is_empty() { while !pending.is_empty() {
@@ -677,13 +677,8 @@ impl Shape {
self.selrect.width() self.selrect.width()
} }
pub fn visually_insignificant( pub fn visually_insignificant(&self, scale: f32, shapes_pool: &ShapesPool) -> bool {
&self, let extrect = self.extrect(shapes_pool);
scale: f32,
shapes_pool: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) -> bool {
let extrect = self.extrect(shapes_pool, modifiers);
extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE
} }
@@ -726,14 +721,10 @@ impl Shape {
self.selrect self.selrect
} }
pub fn extrect( pub fn extrect(&self, shapes_pool: &ShapesPool) -> math::Rect {
&self,
shapes_pool: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) -> math::Rect {
*self *self
.extrect .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 { pub fn get_text_content(&self) -> &TextContent {
@@ -843,12 +834,7 @@ impl Shape {
rect rect
} }
fn apply_children_bounds( fn apply_children_bounds(&self, mut rect: math::Rect, shapes_pool: &ShapesPool) -> math::Rect {
&self,
mut rect: math::Rect,
shapes_pool: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) -> math::Rect {
let include_children = match self.shape_type { let include_children = match self.shape_type {
Type::Group(_) => true, Type::Group(_) => true,
Type::Frame(_) => !self.clip_content, Type::Frame(_) => !self.clip_content,
@@ -858,15 +844,7 @@ impl Shape {
if include_children { if include_children {
for child_id in self.children_ids(false) { for child_id in self.children_ids(false) {
if let Some(child_shape) = shapes_pool.get(&child_id) { if let Some(child_shape) = shapes_pool.get(&child_id) {
// Create a copy of the child shape to apply any transformations rect.join(child_shape.extrect(shapes_pool));
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);
} }
} }
} }
@@ -874,12 +852,8 @@ impl Shape {
rect rect
} }
pub fn calculate_extrect( pub fn calculate_extrect(&self, shapes_pool: &ShapesPool) -> math::Rect {
&self, let shape = self;
shapes_pool: &ShapesPool,
modifiers: &HashMap<Uuid, Matrix>,
) -> math::Rect {
let shape = self.transformed(modifiers.get(&self.id));
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open()); let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
let mut rect = match &shape.shape_type { let mut rect = match &shape.shape_type {
@@ -902,7 +876,7 @@ impl Shape {
rect = self.apply_stroke_bounds(rect, max_stroke); rect = self.apply_stroke_bounds(rect, max_stroke);
rect = self.apply_shadow_bounds(rect); rect = self.apply_shadow_bounds(rect);
rect = self.apply_blur_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 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); let mut shape: Cow<Shape> = Cow::Borrowed(self);
if let Some(transform) = transform { if let Some(transform) = transform {
shape.to_mut().apply_transform(transform); shape.to_mut().apply_transform(transform);
} }
if let Some(structure) = structure {
shape.to_mut().apply_structure(structure);
}
shape.into_owned() shape.into_owned()
} }

View File

@@ -186,11 +186,10 @@ impl ToPath for Shape {
modifiers: &HashMap<Uuid, Matrix>, modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>, structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Path { ) -> Path {
let shape = self.transformed(modifiers.get(&self.id)); match &self.shape_type {
match shape.shape_type {
Type::Frame(ref frame) => { Type::Frame(ref frame) => {
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::new(rect_segments(&shape, frame.corners)); let mut result = Path::new(rect_segments(&self, frame.corners));
for id in children { for id in children {
let Some(shape) = shapes.get(&id) else { let Some(shape) = shapes.get(&id) else {
continue; continue;
@@ -201,7 +200,7 @@ impl ToPath for Shape {
} }
Type::Group(_) => { 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(); let mut result = Path::default();
for id in children { for id in children {
let Some(shape) = shapes.get(&id) else { let Some(shape) = shapes.get(&id) else {
@@ -215,13 +214,13 @@ impl ToPath for Shape {
Path::new(segments) 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(), Type::SVGRaw(_) => Path::default(),
@@ -232,7 +231,7 @@ impl ToPath for Shape {
result = join_paths(result, Path::from_skia_path(path)); 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 // We don't really do a self.shapes.remove so that redo/undo keep working
if let Some(shape) = self.shapes.get(&id) { if let Some(shape) = self.shapes.get(&id) {
let tiles::TileRect(rsx, rsy, rex, rey) = let tiles::TileRect(rsx, rsy, rex, rey) =
self.render_state self.render_state.get_tiles_for_shape(shape, &self.shapes);
.get_tiles_for_shape(shape, &self.shapes, &self.modifiers);
for x in rsx..=rex { for x in rsx..=rex {
for y in rsy..=rey { for y in rsy..=rey {
let tile = tiles::Tile(x, y); let tile = tiles::Tile(x, y);
@@ -159,8 +158,7 @@ impl State {
pub fn update_tile_for_shape(&mut self, shape_id: Uuid) { pub fn update_tile_for_shape(&mut self, shape_id: Uuid) {
if let Some(shape) = self.shapes.get(&shape_id) { if let Some(shape) = self.shapes.get(&shape_id) {
self.render_state self.render_state.update_tile_for(shape, &self.shapes);
.update_tile_for(shape, &self.shapes, &self.modifiers);
} }
} }
@@ -170,7 +168,7 @@ impl State {
}; };
if !shape.id.is_nil() { if !shape.id.is_nil() {
self.render_state 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); .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 self.render_state
.rebuild_modifier_tiles(&mut self.shapes, &self.modifiers); .rebuild_modifier_tiles(&mut self.shapes, ids);
} }
pub fn font_collection(&self) -> &FontCollection { pub fn font_collection(&self) -> &FontCollection {
@@ -217,4 +215,8 @@ impl State {
None 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::shapes::Shape;
use crate::uuid::Uuid; use crate::uuid::Uuid;
use crate::shapes::StructureEntry;
use crate::skia;
use std::cell::OnceCell;
const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3; const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
/// A pool allocator for `Shape` objects that attempts to minimize memory reallocations. /// 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 { pub struct ShapesPool {
shapes: Vec<Shape>, shapes: Vec<Shape>,
shapes_uuid_to_idx: HashMap<Uuid, usize>,
counter: 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 { impl ShapesPool {
@@ -30,6 +40,10 @@ impl ShapesPool {
shapes: vec![], shapes: vec![],
counter: 0, counter: 0,
shapes_uuid_to_idx: HashMap::default(), 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> { pub fn get(&self, id: &Uuid) -> Option<&Shape> {
let idx = *self.shapes_uuid_to_idx.get(id)?; 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)] #[allow(dead_code)]
@@ -87,4 +116,44 @@ impl ShapesPool {
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> { pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> {
self.shapes.iter_mut() 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();
}
} }