Improve shapes pool performance

This commit is contained in:
Alejandro Alonso
2025-10-27 09:21:15 +01:00
committed by alonso.torres
parent ed4df73e42
commit c66a8f5dc5
12 changed files with 159 additions and 102 deletions

View File

@@ -24,7 +24,7 @@ use std::collections::HashMap;
use utils::uuid_from_u32_quartet;
use uuid::Uuid;
pub(crate) static mut STATE: Option<Box<State>> = None;
pub(crate) static mut STATE: Option<Box<State<'static>>> = None;
#[macro_export]
macro_rules! with_state_mut {

View File

@@ -1,7 +1,7 @@
use super::Matrix;
use crate::render::{RenderState, SurfaceId};
use crate::shapes::{BoolType, Path, Segment, Shape, StructureEntry, ToPath, Type};
use crate::state::ShapesPool;
use crate::state::ShapesPoolRef;
use crate::uuid::Uuid;
use bezier_rs::{Bezier, BezierHandles, ProjectionOptions, TValue};
use glam::DVec2;
@@ -387,7 +387,7 @@ fn beziers_to_segments(beziers: &[(BezierSource, Bezier)]) -> Vec<Segment> {
pub fn bool_from_shapes(
bool_type: BoolType,
children_ids: &IndexSet<Uuid>,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Path {
@@ -424,7 +424,7 @@ pub fn bool_from_shapes(
pub fn update_bool_to_path(
shape: &Shape,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Shape {
@@ -449,7 +449,7 @@ pub fn update_bool_to_path(
pub fn debug_render_bool_paths(
render_state: &mut RenderState,
shape: &Shape,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) {

View File

@@ -25,7 +25,7 @@ use crate::shapes::{
all_with_ancestors, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke,
StructureEntry, Type,
};
use crate::state::ShapesPool;
use crate::state::{ShapesPoolMutRef, ShapesPoolRef};
use crate::tiles::{self, PendingTiles, TileRect};
use crate::uuid::Uuid;
use crate::view::Viewbox;
@@ -277,7 +277,7 @@ pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
fn is_modified_child(
shape: &Shape,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
) -> bool {
if modifiers.is_empty() {
@@ -476,7 +476,7 @@ impl RenderState {
#[allow(clippy::too_many_arguments)]
pub fn render_shape(
&mut self,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
shape: &Shape,
@@ -839,7 +839,7 @@ impl RenderState {
pub fn render_from_cache(
&mut self,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) {
@@ -884,7 +884,7 @@ impl RenderState {
pub fn start_render_loop(
&mut self,
tree: &ShapesPool,
tree: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
scale_content: &HashMap<Uuid, f32>,
@@ -944,7 +944,7 @@ impl RenderState {
pub fn process_animation_frame(
&mut self,
tree: &ShapesPool,
tree: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
scale_content: &HashMap<Uuid, f32>,
@@ -1031,7 +1031,7 @@ impl RenderState {
#[inline]
pub fn render_shape_exit(
&mut self,
tree: &ShapesPool,
tree: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
element: &Shape,
@@ -1141,7 +1141,7 @@ impl RenderState {
self.get_rect_bounds(rect)
}
pub fn get_shape_extrect_bounds(&mut self, shape: &Shape, tree: &ShapesPool) -> Rect {
pub fn get_shape_extrect_bounds(&mut self, shape: &Shape, tree: ShapesPoolRef) -> Rect {
let rect = shape.extrect(tree);
self.get_rect_bounds(rect)
}
@@ -1179,7 +1179,7 @@ impl RenderState {
#[allow(clippy::too_many_arguments)]
fn render_drop_black_shadow(
&mut self,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
shape: &Shape,
@@ -1253,7 +1253,7 @@ impl RenderState {
pub fn render_shape_tree_partial_uncached(
&mut self,
tree: &ShapesPool,
tree: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
scale_content: &HashMap<Uuid, f32>,
@@ -1537,7 +1537,7 @@ impl RenderState {
pub fn render_shape_tree_partial(
&mut self,
tree: &ShapesPool,
tree: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
scale_content: &HashMap<Uuid, f32>,
@@ -1655,13 +1655,13 @@ impl RenderState {
Ok(())
}
pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: &ShapesPool) -> TileRect {
pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: ShapesPoolRef) -> TileRect {
let extrect = shape.extrect(tree);
let tile_size = tiles::get_tile_size(self.get_scale());
tiles::get_tiles_for_rect(extrect, tile_size)
}
pub fn update_tile_for(&mut self, shape: &Shape, tree: &ShapesPool) {
pub fn update_tile_for(&mut self, shape: &Shape, tree: ShapesPoolRef) {
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
let old_tiles: HashSet<tiles::Tile> = self
.tiles
@@ -1691,7 +1691,7 @@ impl RenderState {
pub fn rebuild_tiles_shallow(
&mut self,
tree: &ShapesPool,
tree: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) {
@@ -1721,7 +1721,7 @@ impl RenderState {
pub fn rebuild_tiles(
&mut self,
tree: &ShapesPool,
tree: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) {
@@ -1759,7 +1759,7 @@ impl RenderState {
pub fn invalidate_and_update_tiles(
&mut self,
shape_ids: &IndexSet<Uuid>,
tree: &mut ShapesPool,
tree: ShapesPoolMutRef<'_>,
) {
for shape_id in shape_ids {
if let Some(shape) = tree.get(shape_id) {
@@ -1776,7 +1776,7 @@ 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, ids: Vec<Uuid>) {
pub fn rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec<Uuid>) {
let ancestors = all_with_ancestors(&ids, tree, false);
self.invalidate_and_update_tiles(&ancestors, tree);
}

View File

@@ -4,14 +4,14 @@ use std::collections::HashMap;
use crate::math::{Matrix, Rect};
use crate::shapes::modifiers::grid_layout::grid_cell_data;
use crate::shapes::{Shape, StructureEntry};
use crate::state::ShapesPool;
use crate::state::ShapesPoolRef;
use crate::uuid::Uuid;
pub fn render_overlay(
zoom: f32,
canvas: &skia::Canvas,
shape: &Shape,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) {

View File

@@ -1,7 +1,7 @@
use skia_safe::{self as skia, Color4f};
use std::collections::HashMap;
use super::{RenderState, ShapesPool, SurfaceId};
use super::{RenderState, ShapesPoolRef, SurfaceId};
use crate::math::Matrix;
use crate::render::grid_layout;
use crate::shapes::StructureEntry;
@@ -9,7 +9,7 @@ use crate::uuid::Uuid;
pub fn render(
render_state: &mut RenderState,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) {

View File

@@ -50,7 +50,7 @@ pub use transform::*;
use crate::math::{self, Bounds, Matrix, Point};
use indexmap::IndexSet;
use crate::state::ShapesPool;
use crate::state::ShapesPoolRef;
const MIN_VISIBLE_SIZE: f32 = 2.0;
const ANTIALIAS_THRESHOLD: f32 = 15.0;
@@ -197,7 +197,7 @@ pub struct Shape {
// A set of ancestor UUIDs in traversal order (closest ancestor first)
pub fn all_with_ancestors(
shapes: &[Uuid],
shapes_pool: &ShapesPool,
shapes_pool: ShapesPoolRef,
include_hidden: bool,
) -> IndexSet<Uuid> {
let mut pending = Vec::from_iter(shapes.iter());
@@ -677,7 +677,7 @@ impl Shape {
self.selrect.width()
}
pub fn visually_insignificant(&self, scale: f32, shapes_pool: &ShapesPool) -> bool {
pub fn visually_insignificant(&self, scale: f32, shapes_pool: ShapesPoolRef) -> bool {
let extrect = self.extrect(shapes_pool);
extrect.width() * scale < MIN_VISIBLE_SIZE && extrect.height() * scale < MIN_VISIBLE_SIZE
}
@@ -721,7 +721,7 @@ impl Shape {
self.selrect
}
pub fn extrect(&self, shapes_pool: &ShapesPool) -> math::Rect {
pub fn extrect(&self, shapes_pool: ShapesPoolRef) -> math::Rect {
*self
.extrect
.get_or_init(|| self.calculate_extrect(shapes_pool))
@@ -834,7 +834,11 @@ impl Shape {
rect
}
fn apply_children_bounds(&self, mut rect: math::Rect, shapes_pool: &ShapesPool) -> math::Rect {
fn apply_children_bounds(
&self,
mut rect: math::Rect,
shapes_pool: ShapesPoolRef,
) -> math::Rect {
let include_children = match self.shape_type {
Type::Group(_) => true,
Type::Frame(_) => !self.clip_content,
@@ -852,7 +856,7 @@ impl Shape {
rect
}
pub fn calculate_extrect(&self, shapes_pool: &ShapesPool) -> math::Rect {
pub fn calculate_extrect(&self, shapes_pool: ShapesPoolRef) -> math::Rect {
let shape = self;
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
@@ -940,7 +944,7 @@ impl Shape {
pub fn all_children(
&self,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
include_hidden: bool,
include_self: bool,
) -> IndexSet<Uuid> {
@@ -968,7 +972,7 @@ impl Shape {
matrix
}
pub fn get_concatenated_matrix(&self, shapes: &ShapesPool) -> Matrix {
pub fn get_concatenated_matrix(&self, shapes: ShapesPoolRef) -> Matrix {
let mut matrix = Matrix::new_identity();
let mut current_id = self.id;
while let Some(parent_id) = shapes.get(&current_id).and_then(|s| s.parent_id) {
@@ -1179,8 +1183,8 @@ impl Shape {
pub fn transformed(
&self,
transform: Option<&Matrix>,
structure: Option<&Vec<StructureEntry>>) -> Self
{
structure: Option<&Vec<StructureEntry>>,
) -> Self {
let mut shape: Cow<Shape> = Cow::Borrowed(self);
if let Some(transform) = transform {
shape.to_mut().apply_transform(transform);

View File

@@ -13,13 +13,13 @@ use crate::shapes::{
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, StructureEntry,
TransformEntry, Type,
};
use crate::state::{ShapesPool, State};
use crate::state::{ShapesPoolRef, State};
use crate::uuid::Uuid;
#[allow(clippy::too_many_arguments)]
fn propagate_children(
shape: &Shape,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
parent_bounds_before: &Bounds,
parent_bounds_after: &Bounds,
transform: Matrix,
@@ -90,7 +90,7 @@ fn propagate_children(
fn calculate_group_bounds(
shape: &Shape,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &HashMap<Uuid, Bounds>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Option<Bounds> {
@@ -110,7 +110,7 @@ fn calculate_group_bounds(
fn calculate_bool_bounds(
shape: &Shape,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &HashMap<Uuid, Bounds>,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
@@ -464,7 +464,7 @@ mod tests {
let parent_id = Uuid::new_v4();
let shapes = {
let mut shapes = ShapesPool::new();
let mut shapes = ShapesPoolRef::new();
shapes.initialize(10);
let child_id = Uuid::new_v4();
@@ -507,7 +507,7 @@ mod tests {
fn test_group_bounds() {
let parent_id = Uuid::new_v4();
let shapes = {
let mut shapes = ShapesPool::new();
let mut shapes = ShapesPoolRef::new();
shapes.initialize(10);
let child1_id = Uuid::new_v4();

View File

@@ -4,7 +4,7 @@ use crate::shapes::{
AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem,
Modifier, Shape, StructureEntry,
};
use crate::state::ShapesPool;
use crate::state::ShapesPoolRef;
use crate::uuid::Uuid;
use std::collections::{HashMap, VecDeque};
@@ -179,7 +179,7 @@ fn initialize_tracks(
layout_bounds: &Bounds,
layout_axis: &LayoutAxis,
flex_data: &FlexData,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &HashMap<Uuid, Bounds>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Vec<TrackData> {
@@ -433,7 +433,7 @@ fn calculate_track_data(
layout_data: &LayoutData,
flex_data: &FlexData,
layout_bounds: &Bounds,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &HashMap<Uuid, Bounds>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Vec<TrackData> {
@@ -574,7 +574,7 @@ pub fn reflow_flex_layout(
shape: &Shape,
layout_data: &LayoutData,
flex_data: &FlexData,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &mut HashMap<Uuid, Bounds>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> VecDeque<Modifier> {

View File

@@ -4,7 +4,7 @@ use crate::shapes::{
JustifyContent, JustifyItems, JustifySelf, Layout, LayoutData, LayoutItem, Modifier, Shape,
StructureEntry, Type,
};
use crate::state::ShapesPool;
use crate::state::ShapesPoolRef;
use crate::uuid::Uuid;
use indexmap::IndexSet;
use std::collections::{HashMap, VecDeque};
@@ -45,7 +45,7 @@ pub fn calculate_tracks(
grid_data: &GridData,
layout_bounds: &Bounds,
cells: &Vec<GridCell>,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &HashMap<Uuid, Bounds>,
) -> Vec<TrackData> {
let layout_size = if is_column {
@@ -122,7 +122,7 @@ fn set_auto_base_size(
column: bool,
tracks: &mut [TrackData],
cells: &Vec<GridCell>,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &HashMap<Uuid, Bounds>,
) {
for cell in cells {
@@ -173,7 +173,7 @@ fn set_auto_multi_span(
column: bool,
tracks: &mut [TrackData],
cells: &[GridCell],
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &HashMap<Uuid, Bounds>,
) {
// Remove groups with flex (will be set in flex_multi_span)
@@ -248,7 +248,7 @@ fn set_flex_multi_span(
layout_data: &LayoutData,
tracks: &mut [TrackData],
cells: &[GridCell],
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &HashMap<Uuid, Bounds>,
) {
// Remove groups without flex
@@ -539,7 +539,7 @@ fn cell_bounds(
pub fn create_cell_data<'a>(
layout_bounds: &Bounds,
children: &IndexSet<Uuid>,
shapes: &'a ShapesPool,
shapes: ShapesPoolRef<'a>,
cells: &Vec<GridCell>,
column_tracks: &[TrackData],
row_tracks: &[TrackData],
@@ -602,7 +602,7 @@ pub fn create_cell_data<'a>(
pub fn grid_cell_data<'a>(
shape: &Shape,
shapes: &'a ShapesPool,
shapes: ShapesPoolRef<'a>,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
allow_empty: bool,
@@ -723,7 +723,7 @@ pub fn reflow_grid_layout(
shape: &Shape,
layout_data: &LayoutData,
grid_data: &GridData,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
bounds: &mut HashMap<Uuid, Bounds>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> VecDeque<Modifier> {

View File

@@ -4,7 +4,7 @@ use super::{Corners, Path, Segment, Shape, StructureEntry, Type};
use crate::math;
use crate::shapes::text_paths::TextPaths;
use crate::state::ShapesPool;
use crate::state::ShapesPoolRef;
use crate::uuid::Uuid;
use std::collections::HashMap;
@@ -13,7 +13,7 @@ const BEZIER_CIRCLE_C: f32 = 0.551_915_05;
pub trait ToPath {
fn to_path(
&self,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Path;
@@ -182,7 +182,7 @@ fn transform_segments(segments: Vec<Segment>, shape: &Shape) -> Vec<Segment> {
impl ToPath for Shape {
fn to_path(
&self,
shapes: &ShapesPool,
shapes: ShapesPoolRef,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
) -> Path {

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
mod shapes_pool;
mod text_editor;
pub use shapes_pool::*;
pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
pub use text_editor::*;
use crate::render::RenderState;
@@ -19,17 +19,17 @@ use crate::shapes::modifiers::grid_layout::grid_cell_data;
/// It is created by [init] and passed to the other exported functions.
/// Note that rust-skia data structures are not thread safe, so a state
/// must not be shared between different Web Workers.
pub(crate) struct State {
pub(crate) struct State<'a> {
pub render_state: RenderState,
pub text_editor_state: TextEditorState,
pub current_id: Option<Uuid>,
pub shapes: ShapesPool,
pub shapes: ShapesPool<'a>,
pub modifiers: HashMap<Uuid, skia::Matrix>,
pub scale_content: HashMap<Uuid, f32>,
pub structure: HashMap<Uuid, Vec<StructureEntry>>,
}
impl State {
impl<'a> State<'a> {
pub fn new(width: i32, height: i32) -> Self {
State {
render_state: RenderState::new(width, height),
@@ -183,8 +183,16 @@ impl State {
}
pub fn rebuild_modifier_tiles(&mut self, ids: Vec<Uuid>) {
self.render_state
.rebuild_modifier_tiles(&mut self.shapes, ids);
// SAFETY: We're extending the lifetime of the mutable borrow to 'a.
// This is safe because:
// 1. shapes has lifetime 'a in the struct
// 2. The reference won't outlive the struct
// 3. No other references to shapes exist during this call
unsafe {
let shapes_ptr = &mut self.shapes as *mut ShapesPool<'a>;
self.render_state
.rebuild_modifier_tiles(&mut *shapes_ptr, ids);
}
}
pub fn font_collection(&self) -> &FontCollection {

View File

@@ -14,7 +14,7 @@ const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
/// A pool allocator for `Shape` objects that attempts to minimize memory reallocations.
///
/// `ShapesPool` pre-allocates a contiguous vector of `Shape` instances,
/// `ShapesPoolImpl` pre-allocates a contiguous vector of `Shape` instances,
/// which can be reused and indexed efficiently. This design helps avoid
/// memory reallocation overhead by reserving enough space in advance.
///
@@ -23,20 +23,25 @@ const SHAPES_POOL_ALLOC_MULTIPLIER: f32 = 1.3;
/// Shapes are stored in a `Vec<Shape>`, which keeps the `Shape` instances
/// in a contiguous memory block.
///
pub struct ShapesPool {
pub struct ShapesPoolImpl<'a> {
shapes: Vec<Shape>,
counter: usize,
shapes_uuid_to_idx: HashMap<Uuid, usize>,
shapes_uuid_to_idx: HashMap<&'a Uuid, usize>,
modified_shape_cache: HashMap<Uuid, OnceCell<Shape>>,
modifiers: HashMap<Uuid, skia::Matrix>,
structure: HashMap<Uuid, Vec<StructureEntry>>,
modified_shape_cache: HashMap<&'a Uuid, OnceCell<Shape>>,
modifiers: HashMap<&'a Uuid, skia::Matrix>,
structure: HashMap<&'a Uuid, Vec<StructureEntry>>,
}
impl ShapesPool {
// Type aliases to avoid writing lifetimes everywhere
pub type ShapesPool<'a> = ShapesPoolImpl<'a>;
pub type ShapesPoolRef<'a> = &'a ShapesPoolImpl<'a>;
pub type ShapesPoolMutRef<'a> = &'a mut ShapesPoolImpl<'a>;
impl<'a> ShapesPoolImpl<'a> {
pub fn new() -> Self {
ShapesPool {
ShapesPoolImpl {
shapes: vec![],
counter: 0,
shapes_uuid_to_idx: HashMap::default(),
@@ -68,11 +73,19 @@ impl ShapesPool {
self.shapes
.extend(iter::repeat_with(|| Shape::new(Uuid::nil())).take(additional));
}
let new_shape = &mut self.shapes[self.counter];
let idx = self.counter;
let new_shape = &mut self.shapes[idx];
new_shape.id = id;
self.shapes_uuid_to_idx.insert(id, self.counter);
// Get a reference to the id field in the shape
// SAFETY: We need to get a reference with lifetime 'a from the shape's id.
// This is safe because the shapes Vec is stable and won't be reallocated
// (we pre-allocate), and the id field won't move within the Shape.
let id_ref: &'a Uuid = unsafe { &*(&self.shapes[idx].id as *const Uuid) };
self.shapes_uuid_to_idx.insert(id_ref, idx);
self.counter += 1;
new_shape
&mut self.shapes[idx]
}
pub fn len(&self) -> usize {
@@ -80,31 +93,47 @@ impl ShapesPool {
}
pub fn has(&self, id: &Uuid) -> bool {
self.shapes_uuid_to_idx.contains_key(id)
self.shapes_uuid_to_idx.contains_key(&id)
}
pub fn get_mut(&mut self, id: &Uuid) -> Option<&mut Shape> {
let idx = *self.shapes_uuid_to_idx.get(id)?;
let idx = *self.shapes_uuid_to_idx.get(&id)?;
Some(&mut self.shapes[idx])
}
pub fn get(&self, id: &Uuid) -> Option<&Shape> {
let idx = *self.shapes_uuid_to_idx.get(id)?;
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)
)
}))
pub fn get(&self, id: &Uuid) -> Option<&'a Shape> {
let idx = *self.shapes_uuid_to_idx.get(&id)?;
// SAFETY: We're extending the lifetimes to 'a.
// This is safe because:
// 1. All internal HashMaps and the shapes Vec have fields with lifetime 'a
// 2. The shape at idx won't be moved or reallocated (pre-allocated Vec)
// 3. The id is stored in shapes[idx].id which has lifetime 'a
// 4. The references won't outlive the ShapesPoolImpl
unsafe {
let shape_ptr = &self.shapes[idx] as *const Shape;
let modifiers_ptr = &self.modifiers as *const HashMap<&'a Uuid, skia::Matrix>;
let structure_ptr = &self.structure as *const HashMap<&'a Uuid, Vec<StructureEntry>>;
let cache_ptr = &self.modified_shape_cache as *const HashMap<&'a Uuid, OnceCell<Shape>>;
// Extend the lifetime of id to 'a - safe because it's the same Uuid stored in shapes[idx].id
let id_ref: &'a Uuid = &*(id as *const Uuid);
if (*modifiers_ptr).contains_key(&id_ref) || (*structure_ptr).contains_key(&id_ref) {
if let Some(cell) = (*cache_ptr).get(&id_ref) {
Some(cell.get_or_init(|| {
let shape = &*shape_ptr;
shape.transformed(
(*modifiers_ptr).get(&id_ref),
(*structure_ptr).get(&id_ref),
)
}))
} else {
Some(&*shape_ptr)
}
} else {
let shape = &self.shapes[idx];
Some(shape)
Some(&*shape_ptr)
}
} else {
Some(&self.shapes[idx])
}
}
@@ -126,23 +155,30 @@ impl ShapesPool {
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());
// Convert HashMap<Uuid, V> to HashMap<&'a Uuid, V> using references from shapes and
// Initialize the cache cells because later we don't want to have the mutable pointer
let mut modifiers_with_refs = HashMap::with_capacity(modifiers.len());
for (uuid, matrix) in modifiers {
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
self.modified_shape_cache.insert(uuid_ref, OnceCell::new());
modifiers_with_refs.insert(uuid_ref, matrix);
}
}
self.modifiers = modifiers;
self.modifiers = modifiers_with_refs;
}
#[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());
// Convert HashMap<Uuid, V> to HashMap<&'a Uuid, V> using references from shapes and
// Initialize the cache cells because later we don't want to have the mutable pointer
let mut structure_with_refs = HashMap::with_capacity(structure.len());
for (uuid, entries) in structure {
if let Some(uuid_ref) = self.get_uuid_ref(&uuid) {
self.modified_shape_cache.insert(uuid_ref, OnceCell::new());
structure_with_refs.insert(uuid_ref, entries);
}
}
self.structure = structure;
self.structure = structure_with_refs;
}
#[allow(dead_code)]
@@ -156,4 +192,13 @@ impl ShapesPool {
self.clean_shape_cache();
self.structure = HashMap::default();
}
/// Get a reference to the Uuid stored in a shape, if it exists
pub fn get_uuid_ref(&self, id: &Uuid) -> Option<&'a Uuid> {
let idx = *self.shapes_uuid_to_idx.get(&id)?;
// SAFETY: We're returning a reference with lifetime 'a to a Uuid stored
// in the shapes Vec. This is safe because the Vec is stable (pre-allocated)
// and won't be reallocated.
unsafe { Some(&*(&self.shapes[idx].id as *const Uuid)) }
}
}