diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 4c76ed78a2..4d03aadf3b 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; use utils::uuid_from_u32_quartet; use uuid::Uuid; -pub(crate) static mut STATE: Option> = None; +pub(crate) static mut STATE: Option>> = None; #[macro_export] macro_rules! with_state_mut { diff --git a/render-wasm/src/math/bools.rs b/render-wasm/src/math/bools.rs index 5e43764826..8f3facbe57 100644 --- a/render-wasm/src/math/bools.rs +++ b/render-wasm/src/math/bools.rs @@ -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 { pub fn bool_from_shapes( bool_type: BoolType, children_ids: &IndexSet, - shapes: &ShapesPool, + shapes: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, ) -> Path { @@ -424,7 +424,7 @@ pub fn bool_from_shapes( pub fn update_bool_to_path( shape: &Shape, - shapes: &ShapesPool, + shapes: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, ) -> 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, structure: &HashMap>, ) { diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 4dfed1939b..5b1871a2ca 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -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; @@ -276,7 +276,7 @@ pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize { fn is_modified_child( shape: &Shape, - shapes: &ShapesPool, + shapes: ShapesPoolRef, modifiers: &HashMap, ) -> bool { if modifiers.is_empty() { @@ -475,7 +475,7 @@ impl RenderState { #[allow(clippy::too_many_arguments)] pub fn render_shape( &mut self, - shapes: &ShapesPool, + shapes: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, shape: &Shape, @@ -834,7 +834,7 @@ impl RenderState { pub fn render_from_cache( &mut self, - shapes: &ShapesPool, + shapes: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, ) { @@ -879,7 +879,7 @@ impl RenderState { pub fn start_render_loop( &mut self, - tree: &ShapesPool, + tree: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, scale_content: &HashMap, @@ -939,7 +939,7 @@ impl RenderState { pub fn process_animation_frame( &mut self, - tree: &ShapesPool, + tree: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, scale_content: &HashMap, @@ -1022,7 +1022,7 @@ impl RenderState { #[inline] pub fn render_shape_exit( &mut self, - tree: &ShapesPool, + tree: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, element: &Shape, @@ -1134,7 +1134,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) } @@ -1172,7 +1172,7 @@ impl RenderState { #[allow(clippy::too_many_arguments)] fn render_drop_black_shadow( &mut self, - shapes: &ShapesPool, + shapes: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, shape: &Shape, @@ -1246,7 +1246,7 @@ impl RenderState { pub fn render_shape_tree_partial_uncached( &mut self, - tree: &ShapesPool, + tree: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, scale_content: &HashMap, @@ -1530,7 +1530,7 @@ impl RenderState { pub fn render_shape_tree_partial( &mut self, - tree: &ShapesPool, + tree: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, scale_content: &HashMap, @@ -1648,13 +1648,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 = self .tiles @@ -1684,7 +1684,7 @@ impl RenderState { pub fn rebuild_tiles_shallow( &mut self, - tree: &ShapesPool, + tree: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, ) { @@ -1714,7 +1714,7 @@ impl RenderState { pub fn rebuild_tiles( &mut self, - tree: &ShapesPool, + tree: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, ) { @@ -1752,7 +1752,7 @@ impl RenderState { pub fn invalidate_and_update_tiles( &mut self, shape_ids: &IndexSet, - tree: &mut ShapesPool, + tree: ShapesPoolMutRef<'_>, ) { for shape_id in shape_ids { if let Some(shape) = tree.get(shape_id) { @@ -1769,7 +1769,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) { + pub fn rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec) { let ancestors = all_with_ancestors(&ids, tree, false); self.invalidate_and_update_tiles(&ancestors, tree); } diff --git a/render-wasm/src/render/grid_layout.rs b/render-wasm/src/render/grid_layout.rs index 2424d1acca..635cff137d 100644 --- a/render-wasm/src/render/grid_layout.rs +++ b/render-wasm/src/render/grid_layout.rs @@ -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, structure: &HashMap>, ) { diff --git a/render-wasm/src/render/ui.rs b/render-wasm/src/render/ui.rs index 40420f685e..6b37703fdc 100644 --- a/render-wasm/src/render/ui.rs +++ b/render-wasm/src/render/ui.rs @@ -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, structure: &HashMap>, ) { diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 96ed423ea5..fd458da2d4 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -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 { 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 { @@ -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(¤t_id).and_then(|s| s.parent_id) { @@ -1177,8 +1181,8 @@ impl Shape { pub fn transformed( &self, transform: Option<&Matrix>, - structure: Option<&Vec>) -> Self - { + structure: Option<&Vec>, + ) -> Self { let mut shape: Cow = Cow::Borrowed(self); if let Some(transform) = transform { shape.to_mut().apply_transform(transform); diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index c8411621e1..b655342cbc 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -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, structure: &HashMap>, ) -> Option { @@ -110,7 +110,7 @@ fn calculate_group_bounds( fn calculate_bool_bounds( shape: &Shape, - shapes: &ShapesPool, + shapes: ShapesPoolRef, bounds: &HashMap, modifiers: &HashMap, structure: &HashMap>, @@ -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(); diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index af76d5abc6..dd89fde069 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -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, structure: &HashMap>, ) -> Vec { @@ -433,7 +433,7 @@ fn calculate_track_data( layout_data: &LayoutData, flex_data: &FlexData, layout_bounds: &Bounds, - shapes: &ShapesPool, + shapes: ShapesPoolRef, bounds: &HashMap, structure: &HashMap>, ) -> Vec { @@ -574,7 +574,7 @@ pub fn reflow_flex_layout( shape: &Shape, layout_data: &LayoutData, flex_data: &FlexData, - shapes: &ShapesPool, + shapes: ShapesPoolRef, bounds: &mut HashMap, structure: &HashMap>, ) -> VecDeque { diff --git a/render-wasm/src/shapes/modifiers/grid_layout.rs b/render-wasm/src/shapes/modifiers/grid_layout.rs index 8e5a1c2de1..96f017fe79 100644 --- a/render-wasm/src/shapes/modifiers/grid_layout.rs +++ b/render-wasm/src/shapes/modifiers/grid_layout.rs @@ -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, - shapes: &ShapesPool, + shapes: ShapesPoolRef, bounds: &HashMap, ) -> Vec { let layout_size = if is_column { @@ -122,7 +122,7 @@ fn set_auto_base_size( column: bool, tracks: &mut [TrackData], cells: &Vec, - shapes: &ShapesPool, + shapes: ShapesPoolRef, bounds: &HashMap, ) { 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, ) { // 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, ) { // Remove groups without flex @@ -539,7 +539,7 @@ fn cell_bounds( pub fn create_cell_data<'a>( layout_bounds: &Bounds, children: &IndexSet, - shapes: &'a ShapesPool, + shapes: ShapesPoolRef<'a>, cells: &Vec, 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, structure: &HashMap>, 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, structure: &HashMap>, ) -> VecDeque { diff --git a/render-wasm/src/shapes/shape_to_path.rs b/render-wasm/src/shapes/shape_to_path.rs index 8bbcd59bda..a798665abf 100644 --- a/render-wasm/src/shapes/shape_to_path.rs +++ b/render-wasm/src/shapes/shape_to_path.rs @@ -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, structure: &HashMap>, ) -> Path; @@ -182,7 +182,7 @@ fn transform_segments(segments: Vec, shape: &Shape) -> Vec { impl ToPath for Shape { fn to_path( &self, - shapes: &ShapesPool, + shapes: ShapesPoolRef, modifiers: &HashMap, structure: &HashMap>, ) -> Path { diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index a5d7d62b4c..0d82585233 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -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, - pub shapes: ShapesPool, + pub shapes: ShapesPool<'a>, pub modifiers: HashMap, pub scale_content: HashMap, pub structure: HashMap>, } -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) { - 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 { diff --git a/render-wasm/src/state/shapes_pool.rs b/render-wasm/src/state/shapes_pool.rs index 452c33fbab..31d8d83942 100644 --- a/render-wasm/src/state/shapes_pool.rs +++ b/render-wasm/src/state/shapes_pool.rs @@ -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`, which keeps the `Shape` instances /// in a contiguous memory block. /// -pub struct ShapesPool { +pub struct ShapesPoolImpl<'a> { shapes: Vec, counter: usize, - shapes_uuid_to_idx: HashMap, + shapes_uuid_to_idx: HashMap<&'a Uuid, usize>, - modified_shape_cache: HashMap>, - modifiers: HashMap, - structure: HashMap>, + modified_shape_cache: HashMap<&'a Uuid, OnceCell>, + modifiers: HashMap<&'a Uuid, skia::Matrix>, + structure: HashMap<&'a Uuid, Vec>, } -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>; + let cache_ptr = &self.modified_shape_cache as *const HashMap<&'a Uuid, OnceCell>; + + // 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) { // 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 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>) { - // 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 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)) } + } }