diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index ace4d0fc4..1afd3e352 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -167,7 +167,7 @@ ptk/EffectEvent (effect [_ state _] (when (features/active-feature? state "render-wasm/v1") - (wasm.api/set-modifiers nil))) + (wasm.api/clean-modifiers))) ptk/UpdateEvent (update [_ state] @@ -417,6 +417,48 @@ modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)] (assoc state :workspace-modifiers modifiers)))))) +(defn- parse-structure-modifiers + [modif-tree] + (into + [] + (mapcat + (fn [[parent-id data]] + (when (ctm/has-structure? (:modifiers data)) + (->> data + :modifiers + :structure-parent + (mapcat + (fn [modifier] + (case (:type modifier) + :remove-children + (->> (:value modifier) + (map (fn [child-id] + {:type :remove-children + :parent parent-id + :id child-id + :index 0}))) + + :add-children + (->> (:value modifier) + (map (fn [child-id] + {:type :add-children + :parent parent-id + :id child-id + :index (:index modifier)}))) + nil))))))) + modif-tree)) + +(defn- parse-geometry-modifiers + [modif-tree] + (into + [] + (keep + (fn [[id data]] + (when (ctm/has-geometry? (:modifiers data)) + {:id id + :transform (ctm/modifiers->transform (:modifiers data))}))) + modif-tree)) + (defn set-wasm-modifiers ([modif-tree] (set-wasm-modifiers modif-tree false)) @@ -431,15 +473,13 @@ (ptk/reify ::set-wasm-modifiers ptk/EffectEvent (effect [_ _ _] - (let [entries - (->> modif-tree - (mapv (fn [[id data]] - {:id id - :transform (ctm/modifiers->transform (:modifiers data))}))) - - modifiers-new - (wasm.api/propagate-modifiers entries)] - (wasm.api/set-modifiers modifiers-new)))))) + (wasm.api/clean-modifiers) + (let [structure-entries (parse-structure-modifiers modif-tree)] + (wasm.api/set-structure-modifiers structure-entries) + (let [geometry-entries (parse-geometry-modifiers modif-tree) + modifiers-new + (wasm.api/propagate-modifiers geometry-entries)] + (wasm.api/set-modifiers modifiers-new))))))) (defn set-selrect-transform [modifiers] @@ -654,4 +694,3 @@ (if undo-transation? (rx/of (dwu/commit-undo-transaction undo-id)) (rx/empty)))))))) - diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 726e3d4d2..f660c25b6 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -821,6 +821,22 @@ (clear-drawing-cache) (request-render "set-objects"))))))) +(defn set-structure-modifiers + [entries] + (when-not (empty? entries) + (let [offset (mem/alloc-bytes-32 (mem/get-list-size entries 40)) + heapu32 (mem/get-heap-u32)] + (loop [entries (seq entries) + current-offset offset] + (when-not (empty? entries) + (let [{:keys [type parent id index] :as entry} (first entries)] + (sr/heapu32-set-u32 (sr/translate-structure-modifier-type type) heapu32 (+ current-offset 0)) + (sr/heapu32-set-u32 (or index 0) heapu32 (+ current-offset 1)) + (sr/heapu32-set-uuid parent heapu32 (+ current-offset 2)) + (sr/heapu32-set-uuid id heapu32 (+ current-offset 6)) + (recur (rest entries) (+ current-offset 10))))) + (h/call wasm/internal-module "_set_structure_modifiers")))) + (defn propagate-modifiers [entries] (let [offset (mem/alloc-bytes-32 (modifier-get-entries-size entries)) @@ -852,11 +868,13 @@ (h/call wasm/internal-module "_set_canvas_background" rgba) (request-render "set-canvas-background"))) +(defn clean-modifiers + [] + (h/call wasm/internal-module "_clean_modifiers")) + (defn set-modifiers [modifiers] - (if (empty? modifiers) - (h/call wasm/internal-module "_clean_modifiers") - + (when-not (empty? modifiers) (let [offset (mem/alloc-bytes-32 (* MODIFIER-ENTRY-SIZE (count modifiers))) heapu32 (mem/get-heap-u32) heapf32 (mem/get-heap-f32)] diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index 08539274d..e42ff8c45 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -44,6 +44,10 @@ (aset u32-arr 3 (aget buffer 3)) (js/Uint8Array. (.-buffer u32-arr)))) +(defn heapu32-set-u32 + [value heap offset] + (aset heap offset value)) + (defn heapu32-set-uuid [id heap offset] (let [buffer (uuid/get-u32 id)] @@ -262,3 +266,9 @@ :inner-shadow 1 0)) + +(defn translate-structure-modifier-type + [type] + (case type + :remove-children 1 + :add-children 2)) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 2cc17367c..3334be650 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -16,7 +16,7 @@ mod wapi; mod wasm; use crate::mem::SerializableResult; -use crate::shapes::{BoolType, ConstraintH, ConstraintV, TransformEntry, Type}; +use crate::shapes::{BoolType, ConstraintH, ConstraintV, StructureEntry, TransformEntry, Type}; use crate::utils::uuid_from_u32_quartet; use crate::uuid::Uuid; use indexmap::IndexSet; @@ -607,9 +607,31 @@ pub extern "C" fn propagate_modifiers() -> *mut u8 { }); } +#[no_mangle] +pub extern "C" fn set_structure_modifiers() { + let bytes = mem::bytes(); + + let entries: Vec<_> = bytes + .chunks(40) + .map(|data| StructureEntry::from_bytes(data.try_into().unwrap())) + .collect(); + + with_state!(state, { + for entry in entries { + if !state.structure.contains_key(&entry.parent) { + state.structure.insert(entry.parent, Vec::new()); + } + state.structure.get_mut(&entry.parent).unwrap().push(entry); + } + }); + + mem::free_bytes(); +} + #[no_mangle] pub extern "C" fn clean_modifiers() { with_state!(state, { + state.structure.clear(); state.modifiers.clear(); }); } diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 335059475..2ac6bf9f7 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -22,7 +22,7 @@ mod surfaces; mod text; mod tiles; -use crate::shapes::{Corners, Shape, Type}; +use crate::shapes::{modified_children_ids, Corners, Shape, StructureEntry, Type}; use gpu_state::GpuState; use options::RenderOptions; use surfaces::{SurfaceId, Surfaces}; @@ -430,6 +430,7 @@ impl RenderState { &mut self, tree: &mut HashMap, modifiers: &HashMap, + structure: &HashMap>, timestamp: i32, ) -> Result<(), String> { if self.render_in_progress { @@ -487,7 +488,7 @@ impl RenderState { self.current_tile = None; self.render_in_progress = true; self.apply_drawing_to_render_canvas(None); - self.process_animation_frame(tree, modifiers, timestamp)?; + self.process_animation_frame(tree, modifiers, structure, timestamp)?; performance::end_measure!("start_render_loop"); Ok(()) } @@ -496,11 +497,12 @@ impl RenderState { &mut self, tree: &mut HashMap, modifiers: &HashMap, + structure: &HashMap>, timestamp: i32, ) -> Result<(), String> { performance::begin_measure!("process_animation_frame"); if self.render_in_progress { - self.render_shape_tree(tree, modifiers, timestamp)?; + self.render_shape_tree(tree, modifiers, structure, timestamp)?; self.flush(); if self.render_in_progress { @@ -594,11 +596,13 @@ impl RenderState { &mut self, tree: &mut HashMap, modifiers: &HashMap, + structure: &HashMap>, timestamp: i32, ) -> Result<(), String> { if !self.render_in_progress { return Ok(()); } + let scale = self.get_scale(); let mut should_stop = false; @@ -631,6 +635,7 @@ impl RenderState { visited_mask, mask, } = node_render_state; + is_empty = false; let element = tree.get_mut(&node_id).ok_or( "Error: Element with root_id {node_render_state.id} not found in the tree." @@ -709,7 +714,8 @@ impl RenderState { let children_clip_bounds = node_render_state .get_children_clip_bounds(element, modifiers.get(&element.id)); - let mut children_ids = element.children_ids(); + let mut children_ids = + modified_children_ids(element, structure.get(&element.id)); // Z-index ordering on Layouts if element.has_layout() { @@ -757,31 +763,28 @@ impl RenderState { .canvas(SurfaceId::Current) .clear(self.background_color); + let Some(root) = tree.get(&Uuid::nil()) else { + return Err(String::from("Root shape not found")); + }; + let root_ids = modified_children_ids(&root, structure.get(&root.id)); + // If we finish processing every node rendering is complete // let's check if there are more pending nodes if let Some(next_tile_with_distance) = self.pending_tiles.pop() { let (x, y, _) = next_tile_with_distance; let next_tile = (x, y); self.update_render_context(next_tile); + if !self.surfaces.has_cached_tile_surface(next_tile) { if let Some(ids) = self.tiles.get_shapes_at(next_tile) { // We only need first level shapes let mut valid_ids: Vec = ids .iter() - .filter_map(|id| { - tree.get(id) - .filter(|element| element.parent_id == Some(Uuid::nil())) - .map(|_| *id) - }) + .filter_map(|id| root_ids.get(id).map(|_| *id)) .collect(); // These shapes for the tile should be ordered as they are in the parent node - if let Some(root) = tree.get(&Uuid::nil()) { - let root_ids = &root.children_ids(); - valid_ids.sort_by_key(|id| { - root_ids.iter().rev().position(|root_id| root_id == id) - }); - } + valid_ids.sort_by_key(|id| root_ids.get_index_of(id)); self.pending_nodes.extend(valid_ids.into_iter().map(|id| { NodeRenderState { @@ -843,6 +846,7 @@ impl RenderState { &mut self, tree: &mut HashMap, modifiers: &HashMap, + structure: &HashMap>, ) { performance::begin_measure!("rebuild_tiles"); self.tiles.invalidate(); @@ -857,7 +861,9 @@ impl RenderState { } self.update_tile_for(&shape); } - for child_id in shape.children_ids().iter() { + + let children = modified_children_ids(&shape, structure.get(&shape.id)); + for child_id in children.iter() { nodes.push(*child_id); } } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 8cf1f09bf..ac13d7f37 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -2,7 +2,7 @@ use skia_safe::{self as skia}; use crate::render::BlendMode; use crate::uuid::Uuid; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; mod blurs; mod bools; @@ -659,9 +659,9 @@ impl Shape { self.children.first() } - pub fn children_ids(&self) -> Vec { + pub fn children_ids(&self) -> IndexSet { if let Type::Bool(_) = self.shape_type { - vec![] + IndexSet::::new() } else if let Type::Group(group) = self.shape_type { if group.masked { self.children.iter().skip(1).cloned().collect() @@ -858,6 +858,40 @@ impl Shape { } } +/* + Returns the list of children taking into account the structure modifiers +*/ +pub fn modified_children_ids( + element: &Shape, + structure: Option<&Vec>, +) -> IndexSet { + if let Some(structure) = structure { + let mut result: Vec = Vec::from_iter(element.children_ids().iter().map(|id| *id)); + let mut to_remove = HashSet::<&Uuid>::new(); + + for st in structure { + match st.entry_type { + StructureEntryType::AddChild => { + result.insert(st.index as usize, st.id); + } + StructureEntryType::RemoveChild => { + to_remove.insert(&st.id); + } + } + } + + let ret: IndexSet = result + .iter() + .filter(|id| !to_remove.contains(id)) + .map(|id| *id) + .collect(); + + ret + } else { + element.children_ids() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 825f44451..53173dad3 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -8,7 +8,8 @@ use common::GetBounds; use crate::math::{identitish, Bounds, Matrix, Point}; use crate::shapes::{ - ConstraintH, ConstraintV, Frame, Group, Layout, Modifier, Shape, TransformEntry, Type, + modified_children_ids, ConstraintH, ConstraintV, Frame, Group, Layout, Modifier, Shape, + StructureEntry, TransformEntry, Type, }; use crate::state::State; use crate::uuid::Uuid; @@ -20,14 +21,17 @@ fn propagate_children( parent_bounds_after: &Bounds, transform: Matrix, bounds: &HashMap, + structure: &HashMap>, ) -> VecDeque { - if shape.children.len() == 0 || identitish(transform) { + let children_ids = modified_children_ids(shape, structure.get(&shape.id)); + + if children_ids.len() == 0 || identitish(transform) { return VecDeque::new(); } let mut result = VecDeque::new(); - for child_id in shape.children.iter() { + for child_id in children_ids.iter() { let Some(child) = shapes.get(child_id) else { continue; }; @@ -81,10 +85,13 @@ fn calculate_group_bounds( shape: &Shape, shapes: &HashMap, bounds: &HashMap, + structure: &HashMap>, ) -> Option { let shape_bounds = bounds.find(&shape); let mut result = Vec::::new(); - for child_id in shape.children.iter() { + + let children_ids = modified_children_ids(shape, structure.get(&shape.id)); + for child_id in children_ids.iter() { let Some(child) = shapes.get(child_id) else { continue; }; @@ -103,6 +110,13 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec .iter() .map(|entry| Modifier::Transform(entry.clone())) .collect(); + + for (id, _) in &state.structure { + if id != &Uuid::nil() { + entries.push_back(Modifier::Reflow(*id)); + } + } + let mut modifiers = HashMap::::new(); let mut bounds = HashMap::::new(); @@ -133,6 +147,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec &shape_bounds_after, entry.transform, &bounds, + &state.structure, ); entries.append(&mut children); @@ -191,7 +206,9 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec } } Type::Group(Group { masked: true }) => { - if let Some(child) = shapes.get(&shape.children[0]) { + let children_ids = + modified_children_ids(shape, state.structure.get(&shape.id)); + if let Some(child) = shapes.get(&children_ids[0]) { let child_bounds = bounds.find(&child); bounds.insert(shape.id, child_bounds); reflow_parent = true; @@ -199,7 +216,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec } Type::Group(_) => { if let Some(shape_bounds) = - calculate_group_bounds(shape, shapes, &bounds) + calculate_group_bounds(shape, shapes, &bounds, &state.structure) { bounds.insert(shape.id, shape_bounds); reflow_parent = true; @@ -210,7 +227,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec // new path... impossible right now. I'm going to use for the moment the group // calculation if let Some(shape_bounds) = - calculate_group_bounds(shape, shapes, &bounds) + calculate_group_bounds(shape, shapes, &bounds, &state.structure) { bounds.insert(shape.id, shape_bounds); reflow_parent = true; @@ -250,6 +267,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec flex_data, shapes, &mut bounds, + &state.structure, ); entries.append(&mut children); } @@ -313,7 +331,8 @@ mod tests { &bounds_before, &bounds_after, transform, - &HashMap::::new(), + &HashMap::new(), + &HashMap::new(), ); assert_eq!(result.len(), 1); @@ -342,7 +361,7 @@ mod tests { shapes.insert(parent_id, parent.clone()); let bounds = - calculate_group_bounds(&parent, &shapes, &HashMap::::new()).unwrap(); + calculate_group_bounds(&parent, &shapes, &HashMap::new(), &HashMap::new()).unwrap(); assert_eq!(bounds.width(), 3.0); assert_eq!(bounds.height(), 3.0); diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index 7d58a5dd5..51022a35f 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -1,8 +1,8 @@ #![allow(dead_code)] use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt}; use crate::shapes::{ - AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem, - Modifier, Shape, + modified_children_ids, AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, + LayoutData, LayoutItem, Modifier, Shape, StructureEntry, }; use crate::uuid::Uuid; @@ -180,10 +180,11 @@ fn initialize_tracks( flex_data: &FlexData, shapes: &HashMap, bounds: &HashMap, + structure: &HashMap>, ) -> Vec { let mut tracks = Vec::::new(); let mut current_track = TrackData::default(); - let mut children = shape.children.clone(); + let mut children = modified_children_ids(shape, structure.get(&shape.id)); let mut first = true; if !flex_data.is_reverse() { @@ -421,6 +422,7 @@ fn calculate_track_data( layout_bounds: &Bounds, shapes: &HashMap, bounds: &HashMap, + structure: &HashMap>, ) -> Vec { let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data); let mut tracks = initialize_tracks( @@ -430,6 +432,7 @@ fn calculate_track_data( flex_data, shapes, bounds, + structure, ); if !layout_axis.is_auto_main { @@ -550,11 +553,20 @@ pub fn reflow_flex_layout( flex_data: &FlexData, shapes: &HashMap, bounds: &mut HashMap, + structure: &HashMap>, ) -> VecDeque { let mut result = VecDeque::new(); let layout_bounds = &bounds.find(&shape); let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data); - let tracks = calculate_track_data(shape, layout_data, flex_data, layout_bounds, shapes, bounds); + let tracks = calculate_track_data( + shape, + layout_data, + flex_data, + layout_bounds, + shapes, + bounds, + structure, + ); for track in tracks.iter() { let total_shapes_size = track.shapes.iter().map(|s| s.main_size).sum::(); diff --git a/render-wasm/src/shapes/transform.rs b/render-wasm/src/shapes/transform.rs index d8b69b116..7c7be13ec 100644 --- a/render-wasm/src/shapes/transform.rs +++ b/render-wasm/src/shapes/transform.rs @@ -98,6 +98,66 @@ impl SerializableResult for TransformEntry { } } +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum StructureEntryType { + RemoveChild, + AddChild, +} + +impl StructureEntryType { + pub fn from_u32(value: u32) -> Self { + match value { + 1 => Self::RemoveChild, + 2 => Self::AddChild, + _ => unreachable!(), + } + } +} + +#[derive(PartialEq, Debug, Clone)] +#[repr(C)] +pub struct StructureEntry { + pub entry_type: StructureEntryType, + pub index: u32, + pub parent: Uuid, + pub id: Uuid, +} + +impl StructureEntry { + pub fn new(entry_type: StructureEntryType, index: u32, parent: Uuid, id: Uuid) -> Self { + StructureEntry { + entry_type, + index, + parent, + id, + } + } + + pub fn from_bytes(bytes: [u8; 40]) -> Self { + let entry_type = StructureEntryType::from_u32(u32::from_le_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], + ])); + + let index = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]); + + let parent = uuid_from_u32_quartet( + u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]), + u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]), + u32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]), + u32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]), + ); + + let id = uuid_from_u32_quartet( + u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]), + u32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]), + u32::from_le_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]), + u32::from_le_bytes([bytes[36], bytes[37], bytes[38], bytes[39]]), + ); + + StructureEntry::new(entry_type, index, parent, id) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 41872e7b0..f92a5d9de 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -4,6 +4,7 @@ use skia_safe as skia; use crate::render::RenderState; use crate::shapes::Shape; +use crate::shapes::StructureEntry; use crate::uuid::Uuid; /// This struct holds the state of the Rust application between JS calls. @@ -17,6 +18,7 @@ pub(crate) struct State<'a> { pub current_shape: Option<&'a mut Shape>, pub shapes: HashMap, pub modifiers: HashMap, + pub structure: HashMap>, } impl<'a> State<'a> { @@ -27,6 +29,7 @@ impl<'a> State<'a> { current_shape: None, shapes: HashMap::with_capacity(capacity), modifiers: HashMap::new(), + structure: HashMap::new(), } } @@ -39,14 +42,22 @@ impl<'a> State<'a> { } pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> { - self.render_state - .start_render_loop(&mut self.shapes, &self.modifiers, timestamp)?; + self.render_state.start_render_loop( + &mut self.shapes, + &self.modifiers, + &self.structure, + timestamp, + )?; Ok(()) } pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> { - self.render_state - .process_animation_frame(&mut self.shapes, &self.modifiers, timestamp)?; + self.render_state.process_animation_frame( + &mut self.shapes, + &self.modifiers, + &self.structure, + timestamp, + )?; Ok(()) } @@ -109,7 +120,7 @@ impl<'a> State<'a> { pub fn rebuild_tiles(&mut self) { self.render_state - .rebuild_tiles(&mut self.shapes, &self.modifiers); + .rebuild_tiles(&mut self.shapes, &self.modifiers, &self.structure); } pub fn rebuild_modifier_tiles(&mut self) {