diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index e908259586..8c82621296 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -6,7 +6,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::utility_types::{ BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic, Transform, }; -use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer}; +use crate::messages::portfolio::document::utility_types::nodes::{LayerPanelEntry, LayerStructureNode}; use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate}; use crate::messages::prelude::*; use glam::IVec2; @@ -235,12 +235,8 @@ pub enum FrontendMessage { data: LayerPanelEntry, }, UpdateDocumentLayerStructure { - #[serde(rename = "dataBuffer")] - data_buffer: RawBuffer, - }, - UpdateDocumentLayerStructureJs { - #[serde(rename = "dataBuffer")] - data_buffer: JsRawBuffer, + #[serde(rename = "layerStructure")] + layer_structure: Vec, }, UpdateDocumentRulers { origin: (f64, f64), diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index c807b60a36..b49beac8a4 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -3,7 +3,7 @@ use super::node_graph::utility_types::Transform; use super::utility_types::error::EditorError; use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState}; use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus}; -use super::utility_types::nodes::{CollapsedLayers, SelectedNodes}; +use super::utility_types::nodes::{CollapsedLayers, LayerStructureNode, SelectedNodes}; use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid}; use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL}; use crate::messages::input_mapper::utility_types::macros::action_shortcut; @@ -19,7 +19,6 @@ use crate::messages::portfolio::document::properties_panel::properties_panel_mes use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ}; use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate}; -use crate::messages::portfolio::document::utility_types::nodes::RawBuffer; use crate::messages::portfolio::utility_types::{PanelType, PersistentData}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity}; @@ -317,8 +316,8 @@ impl MessageHandler> for DocumentMes DocumentMessage::ClearLayersPanel => { // Send an empty layer list if layers_panel_open { - let data_buffer: RawBuffer = Self::default().serialize_root(); - responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); + let layer_structure = Self::default().layer_structure(); + responses.add(FrontendMessage::UpdateDocumentLayerStructure { layer_structure }); } // Clear the control bar @@ -380,12 +379,12 @@ impl MessageHandler> for DocumentMes DocumentMessage::DocumentStructureChanged => { if layers_panel_open { self.network_interface.load_structure(); - let data_buffer: RawBuffer = self.serialize_root(); + let layer_structure = self.layer_structure(); self.update_layers_panel_control_bar_widgets(layers_panel_open, responses); self.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses); - responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); + responses.add(FrontendMessage::UpdateDocumentLayerStructure { layer_structure }); } } DocumentMessage::DrawArtboardOverlays { context: overlay_context } => { @@ -1891,70 +1890,27 @@ impl DocumentMessageHandler { Ok(document_message_handler) } - /// Called recursively by the entry function [`serialize_root`]. - fn serialize_structure(&self, folder: LayerNodeIdentifier, structure_section: &mut Vec, data_section: &mut Vec, path: &mut Vec) { - let mut space = 0; - for layer_node in folder.children(self.metadata()) { - data_section.push(layer_node.to_node().0); - space += 1; - if layer_node.has_children(self.metadata()) && !self.collapsed.0.contains(&layer_node) { - path.push(layer_node); - - // TODO: Skip if folder is not expanded. - structure_section.push(space); - self.serialize_structure(layer_node, structure_section, data_section, path); - space = 0; - - path.pop(); - } - } - structure_section.push(space | (1 << 63)); + /// Recursively builds the layer structure tree for a folder. + fn build_layer_structure(&self, folder: LayerNodeIdentifier) -> Vec { + folder + .children(self.metadata()) + .map(|layer_node| { + let children = if layer_node.has_children(self.metadata()) && !self.collapsed.0.contains(&layer_node) { + self.build_layer_structure(layer_node) + } else { + Vec::new() + }; + LayerStructureNode { + layer_id: layer_node.to_node(), + children, + } + }) + .collect() } - /// Serializes the layer structure into a condensed 1D structure. - /// - /// # Format - /// It is a string of numbers broken into three sections: - /// - /// | Data | Description | Length | - /// |------------------------------------------------------------------------------------------------------------------------------ |---------------------------------------------------------------|------------------| - /// | `4,` `2, 1, -2, -0,` `16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186` | Encoded example data | | - /// | _____________________________________________________________________________________________________________________________ | _____________________________________________________________ | ________________ | - /// | **Length** section: `4` | Length of the **Structure** section (`L` = `structure.len()`) | First value | - /// | **Structure** section: `2, 1, -2, -0` | The **Structure** section | Next `L` values | - /// | **Data** section: `16533113728871998040, 3427872634365736244, 18115028555707261608, 15878401910454357952, 449479075714955186` | The **Data** section (layer IDs) | Remaining values | - /// - /// The data section lists the layer IDs for all folders/layers in the tree as read from top to bottom. - /// The structure section lists signed numbers. The sign indicates a folder indentation change (`+` is down a level, `-` is up a level). - /// The numbers in the structure block encode the indentation. For example: - /// - `2` means read two elements from the data section, then place a `[`. - /// - `-x` means read `x` elements from the data section and then insert a `]`. - /// - /// ```text - /// 2 V 1 V -2 A -0 A - /// 16533113728871998040,3427872634365736244, 18115028555707261608, 15878401910454357952,449479075714955186 - /// 16533113728871998040,3427872634365736244,[ 18115028555707261608,[15878401910454357952,449479075714955186] ] - /// ``` - /// - /// Resulting layer panel: - /// ```text - /// 16533113728871998040 - /// 3427872634365736244 - /// [3427872634365736244,18115028555707261608] - /// [3427872634365736244,18115028555707261608,15878401910454357952] - /// [3427872634365736244,18115028555707261608,449479075714955186] - /// ``` - pub fn serialize_root(&self) -> RawBuffer { - let mut structure_section = vec![NodeId(0).0]; - let mut data_section = Vec::new(); - self.serialize_structure(LayerNodeIdentifier::ROOT_PARENT, &mut structure_section, &mut data_section, &mut vec![]); - - // Remove the ROOT element. Prepend `L`, the length (excluding the ROOT) of the structure section (which happens to be where the ROOT element was). - structure_section[0] = structure_section.len() as u64 - 1; - // Append the data section to the end. - structure_section.extend(data_section); - - structure_section.as_slice().into() + /// Returns the layer structure as a list of tree nodes, starting from the root. + pub fn layer_structure(&self) -> Vec { + self.build_layer_structure(LayerNodeIdentifier::ROOT_PARENT) } pub fn undo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque) { diff --git a/editor/src/messages/portfolio/document/utility_types/nodes.rs b/editor/src/messages/portfolio/document/utility_types/nodes.rs index 07f262b382..294b633137 100644 --- a/editor/src/messages/portfolio/document/utility_types/nodes.rs +++ b/editor/src/messages/portfolio/document/utility_types/nodes.rs @@ -3,32 +3,14 @@ use super::network_interface::NodeNetworkInterface; use crate::messages::tool::common_functionality::graph_modification_utils; use glam::DVec2; use graph_craft::document::{NodeId, NodeNetwork}; -use serde::ser::SerializeStruct; +/// Represents a node in the layer tree hierarchy, sent to the frontend. +/// Each node contains its layer ID and a list of its visible children. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)] -pub struct RawBuffer(Vec); - -impl From<&[u64]> for RawBuffer { - fn from(iter: &[u64]) -> Self { - let v_from_raw: Vec = iter.iter().flat_map(|x| x.to_ne_bytes()).collect(); - Self(v_from_raw) - } -} -#[derive(Debug, Clone, serde::Deserialize, PartialEq, Eq, specta::Type)] -pub struct JsRawBuffer(Vec); - -impl From for JsRawBuffer { - fn from(buffer: RawBuffer) -> Self { - Self(buffer.0) - } -} -impl serde::Serialize for JsRawBuffer { - fn serialize(&self, serializer: S) -> Result { - let mut buffer = serializer.serialize_struct("Buffer", 2)?; - buffer.serialize_field("pointer", &(self.0.as_ptr() as usize))?; - buffer.serialize_field("length", &(self.0.len()))?; - buffer.end() - } +pub struct LayerStructureNode { + #[serde(rename = "layerId")] + pub layer_id: NodeId, + pub children: Vec, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)] diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index aba662ef31..c24f51b3c1 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -6,12 +6,12 @@ import { patchLayout, UpdateDocumentLayerDetails, - UpdateDocumentLayerStructureJs, + UpdateDocumentLayerStructure, UpdateLayersPanelControlBarLeftLayout, UpdateLayersPanelControlBarRightLayout, UpdateLayersPanelBottomBarLayout, } from "@graphite/messages"; - import type { DataBuffer, LayerPanelEntry, Layout } from "@graphite/messages"; + import type { LayerPanelEntry, LayerStructureNode, Layout } from "@graphite/messages"; import type { NodeGraphState } from "@graphite/state-providers/node-graph"; import type { TooltipState } from "@graphite/state-providers/tooltip"; import { pasteFile } from "@graphite/utility-functions/files"; @@ -91,9 +91,8 @@ layersPanelBottomBarLayout = layersPanelBottomBarLayout; }); - editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerStructureJs, (data) => { - const structure = newUpdateDocumentLayerStructure(data.dataBuffer); - rebuildLayerHierarchy(structure); + editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerStructure, (data) => { + rebuildLayerHierarchy(data.layerStructure); }); editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerDetails, (data) => { @@ -117,7 +116,7 @@ editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelControlBarLeftLayout); editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelControlBarRightLayout); editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelBottomBarLayout); - editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerStructureJs); + editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerStructure); editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerDetails); removeEventListener("pointerup", draggingPointerUp); @@ -130,65 +129,6 @@ removeEventListener("keyup", clippingKeyPress); }); - type DocumentLayerStructure = { - layerId: bigint; - children: DocumentLayerStructure[]; - }; - - function newUpdateDocumentLayerStructure(dataBuffer: DataBuffer): DocumentLayerStructure { - const pointerNum = Number(dataBuffer.pointer); - const lengthNum = Number(dataBuffer.length); - - const wasmMemoryBuffer = editor.raw.buffer; - - // Decode the folder structure encoding - const encoding = new DataView(wasmMemoryBuffer, pointerNum, lengthNum); - - // The structure section indicates how to read through the upcoming layer list and assign depths to each layer - const structureSectionLength = Number(encoding.getBigUint64(0, true)); - const structureSectionMsbSigned = new DataView(wasmMemoryBuffer, pointerNum + 8, structureSectionLength * 8); - - // The layer IDs section lists each layer ID sequentially in the tree, as it will show up in the panel - const layerIdsSection = new DataView(wasmMemoryBuffer, pointerNum + 8 + structureSectionLength * 8); - - let layersEncountered = 0; - let currentFolder: DocumentLayerStructure = { layerId: BigInt(-1), children: [] }; - const currentFolderStack = [currentFolder]; - - for (let i = 0; i < structureSectionLength; i += 1) { - const msbSigned = structureSectionMsbSigned.getBigUint64(i * 8, true); - const msbMask = BigInt(1) << BigInt(64 - 1); - - // Set the MSB to 0 to clear the sign and then read the number as usual - const numberOfLayersAtThisDepth = msbSigned & ~msbMask; - - // Store child folders in the current folder (until we are interrupted by an indent) - for (let j = 0; j < numberOfLayersAtThisDepth; j += 1) { - const layerId = layerIdsSection.getBigUint64(layersEncountered * 8, true); - layersEncountered += 1; - - const childLayer: DocumentLayerStructure = { layerId, children: [] }; - currentFolder.children.push(childLayer); - } - - // Check the sign of the MSB, where a 1 is a negative (outward) indent - const subsequentDirectionOfDepthChange = (msbSigned & msbMask) === BigInt(0); - // Inward - if (subsequentDirectionOfDepthChange) { - currentFolderStack.push(currentFolder); - currentFolder = currentFolder.children[currentFolder.children.length - 1]; - } - // Outward - else { - const popped = currentFolderStack.pop(); - if (!popped) throw Error("Too many negative indents in the folder structure"); - if (popped) currentFolder = popped; - } - } - - return currentFolder; - } - function toggleNodeVisibilityLayerPanel(id: bigint) { editor.handle.toggleNodeVisibilityLayerPanel(id); } @@ -515,7 +455,7 @@ dragInPanel = false; } - function rebuildLayerHierarchy(updateDocumentLayerStructure: DocumentLayerStructure) { + function rebuildLayerHierarchy(layerStructure: LayerStructureNode[]) { const layerWithNameBeingEdited = layers.find((layer: LayerListingInfo) => layer.editingName); const layerIdWithNameBeingEdited = layerWithNameBeingEdited?.entry.id; @@ -523,24 +463,24 @@ layers = []; // Build the new layer hierarchy - const recurse = (folder: DocumentLayerStructure) => { - folder.children.forEach((item, index) => { + const recurse = (children: LayerStructureNode[]) => { + children.forEach((item, index) => { const mapping = layerCache.get(String(item.layerId)); if (mapping) { mapping.id = item.layerId; layers.push({ folderIndex: index, - bottomLayer: index === folder.children.length - 1, + bottomLayer: index === children.length - 1, entry: mapping, editingName: layerIdWithNameBeingEdited === item.layerId, }); } // Call self recursively if there are any children - if (item.children.length >= 1) recurse(item); + if (item.children.length >= 1) recurse(item.children); }); }; - recurse(updateDocumentLayerStructure); + recurse(layerStructure); layers = layers; } diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 89add1e27c..62efc3e5df 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -796,13 +796,13 @@ export class TriggerSaveActiveDocument extends JsMessage { export class DocumentChanged extends JsMessage {} -export type DataBuffer = { - pointer: bigint; - length: bigint; +export type LayerStructureNode = { + layerId: bigint; + children: LayerStructureNode[]; }; -export class UpdateDocumentLayerStructureJs extends JsMessage { - readonly dataBuffer!: DataBuffer; +export class UpdateDocumentLayerStructure extends JsMessage { + readonly layerStructure!: LayerStructureNode[]; } export type TextAlign = "Left" | "Center" | "Right" | "JustifyLeft"; @@ -1717,7 +1717,7 @@ export const messageMakers: Record = { UpdateDocumentArtwork, UpdateDocumentBarLayout, UpdateDocumentLayerDetails, - UpdateDocumentLayerStructureJs, + UpdateDocumentLayerStructure, UpdateDocumentRulers, UpdateDocumentScrollbars, UpdateExportReorderIndex, diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 6877874007..aaa17a1d22 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -154,7 +154,7 @@ impl EditorHandle { } // Sends a FrontendMessage to JavaScript - fn send_frontend_message_to_js(&self, mut message: FrontendMessage) { + fn send_frontend_message_to_js(&self, message: FrontendMessage) { if let FrontendMessage::UpdateImageData { ref image_data } = message { let new_hash = calculate_hash(image_data); let prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed); @@ -166,10 +166,6 @@ impl EditorHandle { return; } - if let FrontendMessage::UpdateDocumentLayerStructure { data_buffer } = message { - message = FrontendMessage::UpdateDocumentLayerStructureJs { data_buffer: data_buffer.into() }; - } - let message_type = message.to_discriminant().local_name(); let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);