diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 843d0bbfd1..1f5cb3618e 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1712,7 +1712,7 @@ impl MessageHandler> for DocumentMes } DocumentMessage::ZoomCanvasToFitAll => { let bounds = if self.graph_view_overlay_open { - self.network_interface.all_nodes_bounding_box(&self.breadcrumb_network_path).cloned() + self.network_interface.all_nodes_bounding_box(&self.breadcrumb_network_path) } else { self.network_interface.document_bounds_document_space(true) }; diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/caches.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/caches.rs index ead9a9372c..c1f5ad897d 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/caches.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/caches.rs @@ -79,7 +79,7 @@ impl NodeNetworkInterface { self.try_get_stack_dependents(network_path) } - pub(crate) fn try_load_stack_dependents(&mut self, network_path: &[NodeId]) { + pub(crate) fn try_load_stack_dependents(&self, network_path: &[NodeId]) { let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in stack_dependents"); return; @@ -90,12 +90,17 @@ impl NodeNetworkInterface { } } - pub(crate) fn try_get_stack_dependents(&self, network_path: &[NodeId]) -> Option<&HashMap> { - let Some(network_metadata) = self.network_metadata(network_path) else { + /// Reads the stack dependents through &self if they are already loaded. + pub(crate) fn with_stack_dependents(&self, network_path: &[NodeId], read: impl FnOnce(&HashMap) -> R) -> Option { + self.network_metadata(network_path)?.transient_metadata.stack_dependents.with_loaded(read) + } + + pub(crate) fn try_get_stack_dependents(&mut self, network_path: &[NodeId]) -> Option<&HashMap> { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Could not get nested network_metadata in try_get_stack_dependents"); return None; }; - let TransientMetadata::Loaded(stack_dependents) = &network_metadata.transient_metadata.stack_dependents else { + let Some(stack_dependents) = network_metadata.transient_metadata.stack_dependents.get_loaded_mut() else { log::error!("could not load stack_dependents"); return None; }; @@ -103,7 +108,7 @@ impl NodeNetworkInterface { } // This function always has to be in sync with the selected nodes. - fn load_stack_dependents(&mut self, network_path: &[NodeId]) { + fn load_stack_dependents(&self, network_path: &[NodeId]) { let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else { log::error!("Could not get selected nodes in load_stack_dependents"); return; @@ -134,15 +139,19 @@ impl NodeNetworkInterface { stack_tops.insert(current_node); break; }; - let Some(outward_wires) = self.outward_wires(network_path) else { + let Some(first_downstream_input) = self.with_outward_wires(network_path, |outward_wires| { + outward_wires + .get(&OutputConnector::node(current_node, 0)) + .map(|layer_outward_wires| layer_outward_wires.first().copied()) + }) else { log::error!("Cannot load outward wires in load_stack_dependents"); return; }; - let Some(layer_outward_wires) = outward_wires.get(&OutputConnector::node(current_node, 0)) else { + let Some(first_downstream_input) = first_downstream_input else { log::error!("Could not get outward_wires for layer {current_node}"); break; }; - match layer_outward_wires.first() { + match first_downstream_input { Some(downstream_input) => { let Some(downstream_node) = downstream_input.node_id() else { log::error!("Node connected to export should be absolute"); @@ -174,15 +183,11 @@ impl NodeNetworkInterface { owned_sole_dependents.insert(*layer_sole_dependent); new_owned_nodes.insert(*layer_sole_dependent); } - let Some(layer_node) = self.node_metadata_mut(&upstream_layer, network_path) else { + let Some(layer_node) = self.node_metadata(&upstream_layer, network_path) else { log::error!("Could not get layer node in load_stack_dependents"); continue; }; - let NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { owned_nodes, .. }) = &mut layer_node.persistent_metadata.node_type_metadata else { - log::error!("upstream layer should be a layer"); - return; - }; - *owned_nodes = TransientMetadata::Loaded(new_owned_nodes); + layer_node.transient_metadata.owned_nodes.store(new_owned_nodes); } } } @@ -222,12 +227,12 @@ impl NodeNetworkInterface { } } - let Some(network_metadata) = self.network_metadata_mut(network_path) else { - log::error!("Could not get current network in load_export_ports"); + let Some(network_metadata) = self.network_metadata(network_path) else { + log::error!("Could not get current network in load_stack_dependents"); return; }; - network_metadata.transient_metadata.stack_dependents = TransientMetadata::Loaded(stack_dependents); + network_metadata.transient_metadata.stack_dependents.store(stack_dependents); } pub fn unload_stack_dependents(&mut self, network_path: &[NodeId]) { @@ -245,7 +250,7 @@ impl NodeNetworkInterface { return; }; - if let TransientMetadata::Loaded(stack_dependents) = &mut network_metadata.transient_metadata.stack_dependents { + if let Some(stack_dependents) = network_metadata.transient_metadata.stack_dependents.get_loaded_mut() { for layer_owner in stack_dependents.values_mut() { if let LayerOwner::None(offset) = layer_owner { *offset = 0; @@ -255,26 +260,36 @@ impl NodeNetworkInterface { } pub fn import_export_ports(&mut self, network_path: &[NodeId]) -> Option<&Ports> { - let Some(network_metadata) = self.network_metadata(network_path) else { - log::error!("Could not get nested network_metadata in export_ports"); - return None; - }; - if !network_metadata.transient_metadata.import_export_ports.is_loaded() { - self.load_import_export_ports(network_path); - } + self.try_load_import_export_ports(network_path); - let Some(network_metadata) = self.network_metadata(network_path) else { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Could not get nested network_metadata in export_ports"); return None; }; - let TransientMetadata::Loaded(ports) = &network_metadata.transient_metadata.import_export_ports else { + let Some(ports) = network_metadata.transient_metadata.import_export_ports.get_loaded_mut() else { log::error!("could not load import ports"); return None; }; Some(ports) } - pub fn load_import_export_ports(&mut self, network_path: &[NodeId]) { + /// Reads the import/export ports through &self, loading them first if needed. + pub(crate) fn with_import_export_ports(&self, network_path: &[NodeId], read: impl FnOnce(&Ports) -> R) -> Option { + self.try_load_import_export_ports(network_path); + self.network_metadata(network_path)?.transient_metadata.import_export_ports.with_loaded(read) + } + + fn try_load_import_export_ports(&self, network_path: &[NodeId]) { + let Some(network_metadata) = self.network_metadata(network_path) else { + log::error!("Could not get nested network_metadata in export_ports"); + return; + }; + if !network_metadata.transient_metadata.import_export_ports.is_loaded() { + self.load_import_export_ports(network_path); + } + } + + pub fn load_import_export_ports(&self, network_path: &[NodeId]) { let Some(import_export_position) = self.import_export_position(network_path) else { log::error!("Could not get import_export_position"); return; @@ -294,12 +309,12 @@ impl NodeNetworkInterface { import_export_ports.insert_input_port_at_center(export_index, import_export_position.1.as_dvec2() + DVec2::new(0., export_index as f64 * 24.)); } - let Some(network_metadata) = self.network_metadata_mut(network_path) else { + let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get current network in load_export_ports"); return; }; - network_metadata.transient_metadata.import_export_ports = TransientMetadata::Loaded(import_export_ports); + network_metadata.transient_metadata.import_export_ports.store(import_export_ports); } pub(crate) fn unload_import_export_ports(&mut self, network_path: &[NodeId]) { @@ -342,74 +357,75 @@ impl NodeNetworkInterface { if !network_metadata.transient_metadata.modify_import_export.is_loaded() { self.load_modify_import_export(network_path); } - let Some(network_metadata) = self.network_metadata(network_path) else { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Could not get nested network_metadata in modify_import_export"); return None; }; - let TransientMetadata::Loaded(click_targets) = &network_metadata.transient_metadata.modify_import_export else { + let Some(click_targets) = network_metadata.transient_metadata.modify_import_export.get_loaded_mut() else { log::error!("could not load modify import export ports"); return None; }; Some(click_targets) } - pub fn load_modify_import_export(&mut self, network_path: &[NodeId]) { + pub fn load_modify_import_export(&self, network_path: &[NodeId]) { let mut reorder_imports_exports = Ports::new(); let mut remove_imports_exports = Ports::new(); if !network_path.is_empty() { - let Some(import_exports) = self.import_export_ports(network_path) else { + let ports_built = self.with_import_export_ports(network_path, |import_exports| { + for (import_index, import_click_target) in import_exports.output_ports() { + let Some(import_bounding_box) = import_click_target.bounding_box() else { + log::error!("Could not get export bounding box in load_modify_import_export"); + continue; + }; + let reorder_import_center = (import_bounding_box[0] + import_bounding_box[1]) / 2. + DVec2::new(-12., 0.); + + if *import_index == 0 { + let remove_import_center = reorder_import_center + DVec2::new(-4., 0.); + let remove_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_import_center - DVec2::new(8., 8.), remove_import_center + DVec2::new(8., 8.)), 0.); + remove_imports_exports.insert_custom_output_port(*import_index, remove_import); + } else { + let remove_import_center = reorder_import_center + DVec2::new(-12., 0.); + let reorder_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(reorder_import_center - DVec2::new(3., 4.), reorder_import_center + DVec2::new(3., 4.)), 0.); + let remove_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_import_center - DVec2::new(8., 8.), remove_import_center + DVec2::new(8., 8.)), 0.); + reorder_imports_exports.insert_custom_output_port(*import_index, reorder_import); + remove_imports_exports.insert_custom_output_port(*import_index, remove_import); + } + } + + for (export_index, export_click_target) in import_exports.input_ports() { + let Some(export_bounding_box) = export_click_target.bounding_box() else { + log::error!("Could not get export bounding box in load_modify_import_export"); + continue; + }; + let reorder_export_center = (export_bounding_box[0] + export_bounding_box[1]) / 2. + DVec2::new(12., 0.); + + if *export_index == 0 { + let remove_export_center = reorder_export_center + DVec2::new(4., 0.); + let remove_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_export_center - DVec2::new(8., 8.), remove_export_center + DVec2::new(8., 8.)), 0.); + remove_imports_exports.insert_custom_input_port(*export_index, remove_export); + } else { + let remove_export_center = reorder_export_center + DVec2::new(12., 0.); + let reorder_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(reorder_export_center - DVec2::new(3., 4.), reorder_export_center + DVec2::new(3., 4.)), 0.); + let remove_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_export_center - DVec2::new(8., 8.), remove_export_center + DVec2::new(8., 8.)), 0.); + reorder_imports_exports.insert_custom_input_port(*export_index, reorder_export); + remove_imports_exports.insert_custom_input_port(*export_index, remove_export); + } + } + }); + if ports_built.is_none() { log::error!("Could not get import_export_ports in load_modify_import_export"); return; - }; - - for (import_index, import_click_target) in import_exports.output_ports() { - let Some(import_bounding_box) = import_click_target.bounding_box() else { - log::error!("Could not get export bounding box in load_modify_import_export"); - continue; - }; - let reorder_import_center = (import_bounding_box[0] + import_bounding_box[1]) / 2. + DVec2::new(-12., 0.); - - if *import_index == 0 { - let remove_import_center = reorder_import_center + DVec2::new(-4., 0.); - let remove_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_import_center - DVec2::new(8., 8.), remove_import_center + DVec2::new(8., 8.)), 0.); - remove_imports_exports.insert_custom_output_port(*import_index, remove_import); - } else { - let remove_import_center = reorder_import_center + DVec2::new(-12., 0.); - let reorder_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(reorder_import_center - DVec2::new(3., 4.), reorder_import_center + DVec2::new(3., 4.)), 0.); - let remove_import = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_import_center - DVec2::new(8., 8.), remove_import_center + DVec2::new(8., 8.)), 0.); - reorder_imports_exports.insert_custom_output_port(*import_index, reorder_import); - remove_imports_exports.insert_custom_output_port(*import_index, remove_import); - } - } - - for (export_index, export_click_target) in import_exports.input_ports() { - let Some(export_bounding_box) = export_click_target.bounding_box() else { - log::error!("Could not get export bounding box in load_modify_import_export"); - continue; - }; - let reorder_export_center = (export_bounding_box[0] + export_bounding_box[1]) / 2. + DVec2::new(12., 0.); - - if *export_index == 0 { - let remove_export_center = reorder_export_center + DVec2::new(4., 0.); - let remove_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_export_center - DVec2::new(8., 8.), remove_export_center + DVec2::new(8., 8.)), 0.); - remove_imports_exports.insert_custom_input_port(*export_index, remove_export); - } else { - let remove_export_center = reorder_export_center + DVec2::new(12., 0.); - let reorder_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(reorder_export_center - DVec2::new(3., 4.), reorder_export_center + DVec2::new(3., 4.)), 0.); - let remove_export = ClickTarget::new_with_subpath(Subpath::new_rectangle(remove_export_center - DVec2::new(8., 8.), remove_export_center + DVec2::new(8., 8.)), 0.); - reorder_imports_exports.insert_custom_input_port(*export_index, reorder_export); - remove_imports_exports.insert_custom_input_port(*export_index, remove_export); - } } } - let Some(network_metadata) = self.network_metadata_mut(network_path) else { + let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get current network in load_modify_import_export"); return; }; - network_metadata.transient_metadata.modify_import_export = TransientMetadata::Loaded(ModifyImportExportClickTarget { + network_metadata.transient_metadata.modify_import_export.store(ModifyImportExportClickTarget { remove_imports_exports, reorder_imports_exports, }); @@ -423,18 +439,16 @@ impl NodeNetworkInterface { network_metadata.transient_metadata.modify_import_export.unload(); } - pub(crate) fn owned_nodes(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&HashSet> { + /// Reads the owned nodes of a layer through &self if they are loaded. + pub(crate) fn with_owned_nodes(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&HashSet) -> R) -> Option { let layer_node = self.node_metadata(node_id, network_path)?; - let NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { owned_nodes, .. }) = &layer_node.persistent_metadata.node_type_metadata else { + if !layer_node.persistent_metadata.is_layer() { return None; - }; - let TransientMetadata::Loaded(owned_nodes) = owned_nodes else { - return None; - }; - Some(owned_nodes) + } + layer_node.transient_metadata.owned_nodes.with_loaded(read) } - pub fn all_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<&[DVec2; 2]> { + pub fn all_nodes_bounding_box(&self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in all_nodes_bounding_box"); return None; @@ -444,17 +458,14 @@ impl NodeNetworkInterface { self.load_all_nodes_bounding_box(network_path); } - let network_metadata = self.network_metadata(network_path)?; - - let TransientMetadata::Loaded(bounding_box) = &network_metadata.transient_metadata.all_nodes_bounding_box else { + let bounding_box = self.network_metadata(network_path)?.transient_metadata.all_nodes_bounding_box.with_loaded(|bounds| *bounds); + if bounding_box.is_none() { log::error!("could not load all nodes bounding box"); - return None; - }; - - Some(bounding_box) + } + bounding_box } - pub fn load_all_nodes_bounding_box(&mut self, network_path: &[NodeId]) { + pub fn load_all_nodes_bounding_box(&self, network_path: &[NodeId]) { let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in load_all_nodes_bounding_box"); return; @@ -463,15 +474,12 @@ impl NodeNetworkInterface { let all_nodes_bounding_box = nodes .iter() - .filter_map(|node_id| { - self.node_click_targets(node_id, network_path) - .and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box()) - }) + .filter_map(|node_id| self.node_bounding_box(node_id, network_path)) .reduce(Quad::combine_bounds) .unwrap_or([DVec2::new(0., 0.), DVec2::new(0., 0.)]); - let Some(network_metadata) = self.network_metadata_mut(network_path) else { return }; - network_metadata.transient_metadata.all_nodes_bounding_box = TransientMetadata::Loaded(all_nodes_bounding_box); + let Some(network_metadata) = self.network_metadata(network_path) else { return }; + network_metadata.transient_metadata.all_nodes_bounding_box.store(all_nodes_bounding_box); } pub fn unload_all_nodes_bounding_box(&mut self, network_path: &[NodeId]) { @@ -484,18 +492,13 @@ impl NodeNetworkInterface { } pub fn outward_wires(&mut self, network_path: &[NodeId]) -> Option<&HashMap>> { - let Some(network_metadata) = self.network_metadata(network_path) else { + self.try_load_outward_wires(network_path); + + let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Could not get nested network_metadata in outward_wires"); return None; }; - - if !network_metadata.transient_metadata.outward_wires.is_loaded() { - self.load_outward_wires(network_path); - } - - let network_metadata = self.network_metadata(network_path)?; - - let TransientMetadata::Loaded(outward_wires) = &network_metadata.transient_metadata.outward_wires else { + let Some(outward_wires) = network_metadata.transient_metadata.outward_wires.get_loaded_mut() else { log::error!("could not load outward wires"); return None; }; @@ -503,7 +506,23 @@ impl NodeNetworkInterface { Some(outward_wires) } - fn load_outward_wires(&mut self, network_path: &[NodeId]) { + /// Reads the outward wires through &self, loading them first if needed. + pub(crate) fn with_outward_wires(&self, network_path: &[NodeId], read: impl FnOnce(&HashMap>) -> R) -> Option { + self.try_load_outward_wires(network_path); + self.network_metadata(network_path)?.transient_metadata.outward_wires.with_loaded(read) + } + + fn try_load_outward_wires(&self, network_path: &[NodeId]) { + let Some(network_metadata) = self.network_metadata(network_path) else { + log::error!("Could not get nested network_metadata in outward_wires"); + return; + }; + if !network_metadata.transient_metadata.outward_wires.is_loaded() { + self.load_outward_wires(network_path); + } + } + + fn load_outward_wires(&self, network_path: &[NodeId]) { let mut outward_wires = HashMap::new(); let Some(network) = self.nested_network(network_path) else { log::error!("Could not get nested network in load_outward_wires"); @@ -546,13 +565,13 @@ impl NodeNetworkInterface { } } - let Some(network_metadata) = self.network_metadata_mut(network_path) else { return }; + let Some(network_metadata) = self.network_metadata(network_path) else { return }; - network_metadata.transient_metadata.outward_wires = TransientMetadata::Loaded(outward_wires); + network_metadata.transient_metadata.outward_wires.store(outward_wires); } pub(crate) fn unload_outward_wires(&mut self, network_path: &[NodeId]) { - let Some(network_metadata) = self.network_metadata_mut(network_path) else { + let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in unload_outward_wires"); return; }; @@ -566,7 +585,7 @@ impl NodeNetworkInterface { let Some(network_metadata) = self.network_metadata_mut(network_path) else { return; }; - let TransientMetadata::Loaded(outward_wires) = &mut network_metadata.transient_metadata.outward_wires else { + let Some(outward_wires) = network_metadata.transient_metadata.outward_wires.get_loaded_mut() else { return; }; @@ -583,7 +602,7 @@ impl NodeNetworkInterface { } } - pub fn layer_width(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { + pub fn layer_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { let Some(node_metadata) = self.node_metadata(node_id, network_path) else { log::error!("Could not get nested node_metadata in layer_width"); return None; @@ -593,29 +612,15 @@ impl NodeNetworkInterface { return None; } - let layer_width_loaded = if let NodeTypeTransientMetadata::Layer(layer_metadata) = &node_metadata.transient_metadata.node_type_metadata { - layer_metadata.layer_width.is_loaded() - } else { - false - }; - if !layer_width_loaded { + if !node_metadata.transient_metadata.layer_width.is_loaded() { self.load_layer_width(node_id, network_path); } let node_metadata = self.node_metadata(node_id, network_path)?; - let NodeTypeTransientMetadata::Layer(layer_metadata) = &node_metadata.transient_metadata.node_type_metadata else { - log::error!("Transient metadata should be layer metadata when getting layer width"); - return None; - }; - let TransientMetadata::Loaded(layer_width) = layer_metadata.layer_width else { - log::error!("Transient metadata was not loaded when getting layer width"); - return None; - }; - - Some(layer_width) + node_metadata.transient_metadata.layer_width.with_loaded(|layer_width| *layer_width) } - pub fn load_layer_width(&mut self, node_id: &NodeId, network_path: &[NodeId]) { + pub fn load_layer_width(&self, node_id: &NodeId, network_path: &[NodeId]) { const GAP_WIDTH: f64 = 8.; const FONT_SIZE: f64 = 14.; let left_thumbnail_padding = GRID_SIZE as f64 / 2.; @@ -632,21 +637,14 @@ impl NodeNetworkInterface { let layer_width_pixels = left_thumbnail_padding + thumbnail_width + GAP_WIDTH + text_width + grip_padding + grip_width + lock_icon_width + icon_overhang_width; let layer_width = ((layer_width_pixels / 24.).ceil() as u32).max(8); - let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { + let Some(node_metadata) = self.node_metadata(node_id, network_path) else { log::error!("Could not get nested node_metadata in load_layer_width"); return; }; // Ensure layer width is not loaded for a non layer node if node_metadata.persistent_metadata.is_layer() { - if let NodeTypeTransientMetadata::Layer(layer_metadata) = &mut node_metadata.transient_metadata.node_type_metadata { - layer_metadata.layer_width = TransientMetadata::Loaded(layer_width); - } else { - // Set the entire transient node type metadata to be a layer, in case it was previously a node - node_metadata.transient_metadata.node_type_metadata = NodeTypeTransientMetadata::Layer(LayerTransientMetadata { - layer_width: TransientMetadata::Loaded(layer_width), - }); - } + node_metadata.transient_metadata.layer_width.store(layer_width); } else { log::warn!("Tried loading layer width for non layer node"); } @@ -661,95 +659,68 @@ impl NodeNetworkInterface { }; // If the node is a layer, then the width and click targets need to be recalculated - if is_layer && let NodeTypeTransientMetadata::Layer(layer_metadata) = &mut node_metadata.transient_metadata.node_type_metadata { - layer_metadata.layer_width.unload(); + if is_layer { + node_metadata.transient_metadata.layer_width.unload(); } } - pub fn get_input_center(&mut self, input: &InputConnector, network_path: &[NodeId]) -> Option { - let (ports, index) = match input { + pub fn get_input_center(&self, input: &InputConnector, network_path: &[NodeId]) -> Option { + fn port_center(ports: &Ports, index: usize) -> Option { + ports + .input_ports + .iter() + .find_map(|(input_index, click_target)| if index == *input_index { click_target.bounding_box_center() } else { None }) + } + + match input { InputConnector::Node { node_id, input_index } => { - let node_click_target = self.node_click_targets(node_id, network_path)?; - (&node_click_target.port_click_targets, input_index) + self.try_load_node_click_targets(node_id, network_path); + self.with_node_click_targets(node_id, network_path, |click_targets| port_center(&click_targets.port_click_targets, *input_index)) + .flatten() } - InputConnector::Export(export_index) => { - let ports = self.import_export_ports(network_path)?; - (ports, export_index) - } - }; - ports - .input_ports - .iter() - .find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None }) + InputConnector::Export(export_index) => self.with_import_export_ports(network_path, |ports| port_center(ports, *export_index)).flatten(), + } } - pub fn get_output_center(&mut self, output: &OutputConnector, network_path: &[NodeId]) -> Option { - let (ports, index) = match output { + pub fn get_output_center(&self, output: &OutputConnector, network_path: &[NodeId]) -> Option { + fn port_center(ports: &Ports, index: usize) -> Option { + ports + .output_ports + .iter() + .find_map(|(output_index, click_target)| if index == *output_index { click_target.bounding_box_center() } else { None }) + } + + match output { OutputConnector::Node { node_id, output_index } => { - let node_click_target = self.node_click_targets(node_id, network_path)?; - (&node_click_target.port_click_targets, output_index) + self.try_load_node_click_targets(node_id, network_path); + self.with_node_click_targets(node_id, network_path, |click_targets| port_center(&click_targets.port_click_targets, *output_index)) + .flatten() } - OutputConnector::Import(import_index) => { - let ports = self.import_export_ports(network_path)?; - (ports, import_index) - } - }; - ports - .output_ports - .iter() - .find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None }) + OutputConnector::Import(import_index) => self.with_import_export_ports(network_path, |ports| port_center(ports, *import_index)).flatten(), + } } - pub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { + pub fn newly_loaded_input_wire(&self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { if !self.wire_is_loaded(input, network_path) { self.load_wire(input, graph_wire_style, network_path); } else { return None; } - let wire = match input { - InputConnector::Node { node_id, input_index } => { - let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?; - let TransientMetadata::Loaded(wire) = &input_metadata.wire else { - log::error!("Could not load wire for input: {input:?}"); - return None; - }; - wire.clone() - } - InputConnector::Export(export_index) => { - let network_metadata = self.network_metadata(network_path)?; - let Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else { - log::error!("Could not load wire for input: {input:?}"); - return None; - }; - wire.clone() - } + let network_metadata = self.network_metadata(network_path)?; + let Some(wire) = network_metadata.transient_metadata.wires.borrow().get(input).cloned() else { + log::error!("Could not load wire for input: {input:?}"); + return None; }; Some(wire) } - pub fn wire_is_loaded(&mut self, input: &InputConnector, network_path: &[NodeId]) -> bool { - match input { - InputConnector::Node { node_id, input_index } => { - let Some(input_metadata) = self.transient_input_metadata(node_id, *input_index, network_path) else { - log::error!("Input metadata should always exist for input"); - return false; - }; - input_metadata.wire.is_loaded() - } - InputConnector::Export(export_index) => { - let Some(network_metadata) = self.network_metadata(network_path) else { - return false; - }; - match network_metadata.transient_metadata.wires.get(*export_index) { - Some(wire) => wire.is_loaded(), - None => false, - } - } - } + pub fn wire_is_loaded(&self, input: &InputConnector, network_path: &[NodeId]) -> bool { + self.network_metadata(network_path) + .is_some_and(|network_metadata| network_metadata.transient_metadata.wires.borrow().contains_key(input)) } - fn load_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) { + fn load_wire(&self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) { let dashed = match self.previewing(network_path) { Previewing::Yes { .. } => match input { InputConnector::Node { .. } => false, @@ -761,36 +732,18 @@ impl NodeNetworkInterface { log::error!("Could not load wire path from input"); return; }; - match input { - InputConnector::Node { node_id, input_index } => { - let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { return }; - let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { - // log::warn!("Node metadata must exist on node: {input:?}"); - return; - }; - let wire_update = WirePathUpdate { - id: *node_id, - input_index: *input_index, - wire_path_update: Some(wire), - }; - input_metadata.transient_metadata.wire = TransientMetadata::Loaded(wire_update); - } - InputConnector::Export(export_index) => { - let Some(network_metadata) = self.network_metadata_mut(network_path) else { return }; - if *export_index >= network_metadata.transient_metadata.wires.len() { - network_metadata.transient_metadata.wires.resize(export_index + 1, TransientMetadata::Unloaded); - } - let Some(input_metadata) = network_metadata.transient_metadata.wires.get_mut(*export_index) else { - return; - }; - let wire_update = WirePathUpdate { - id: NodeId(u64::MAX), - input_index: *export_index, - wire_path_update: Some(wire), - }; - *input_metadata = TransientMetadata::Loaded(wire_update); - } - } + let (id, input_index) = match input { + InputConnector::Node { node_id, input_index } => (*node_id, *input_index), + InputConnector::Export(export_index) => (NodeId(u64::MAX), *export_index), + }; + let wire_update = WirePathUpdate { + id, + input_index, + wire_path_update: Some(wire), + }; + + let Some(network_metadata) = self.network_metadata(network_path) else { return }; + network_metadata.transient_metadata.wires.borrow_mut().insert(*input, wire_update); } pub fn all_input_connectors(&self, network_path: &[NodeId]) -> Vec { @@ -851,34 +804,14 @@ impl NodeNetworkInterface { } pub fn unload_wire(&mut self, input: &InputConnector, network_path: &[NodeId]) { - match input { - InputConnector::Node { node_id, input_index } => { - let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { - return; - }; - let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { - // log::warn!("Node metadata must exist on node: {input:?}"); - return; - }; - input_metadata.transient_metadata.wire = TransientMetadata::Unloaded; - } - InputConnector::Export(export_index) => { - let Some(network_metadata) = self.network_metadata_mut(network_path) else { - return; - }; - if *export_index >= network_metadata.transient_metadata.wires.len() { - network_metadata.transient_metadata.wires.resize(export_index + 1, TransientMetadata::Unloaded); - } - let Some(input_metadata) = network_metadata.transient_metadata.wires.get_mut(*export_index) else { - return; - }; - *input_metadata = TransientMetadata::Unloaded; - } - } + let Some(network_metadata) = self.network_metadata(network_path) else { + return; + }; + network_metadata.transient_metadata.wires.borrow_mut().remove(input); } /// When previewing, there may be a second path to the root node. - pub fn wire_to_root(&mut self, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { + pub fn wire_to_root(&self, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { let input = InputConnector::Export(0); let current_export = self.upstream_output_connector(&input, network_path)?; @@ -927,7 +860,7 @@ impl NodeNetworkInterface { } /// Returns the wire subpath, its thick center-line subpath, and whether the wire should be thick. - pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(BezPath, BezPath, bool)> { + pub fn vector_wire_from_input(&self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(BezPath, BezPath, bool)> { let Some(input_position) = self.get_input_center(input, network_path) else { log::error!("Could not get dom rect for wire end: {input:?}"); return None; @@ -948,7 +881,7 @@ impl NodeNetworkInterface { Some((vector_wire, center_line, thick)) } - pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option { + pub fn wire_path_from_input(&self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option { let (vector_wire, center_line, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?; let path_string = vector_wire.to_svg(); let center_path_string = center_line.to_svg(); @@ -971,10 +904,16 @@ impl NodeNetworkInterface { pub fn node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeClickTargets> { self.try_load_node_click_targets(node_id, network_path); - self.try_get_node_click_targets(node_id, network_path) + + let node_metadata = self.node_metadata_mut(node_id, network_path)?; + let Some(click_targets) = node_metadata.transient_metadata.click_targets.get_loaded_mut() else { + log::error!("Could not load node type metadata when getting click targets"); + return None; + }; + Some(click_targets) } - fn try_load_node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) { + pub(crate) fn try_load_node_click_targets(&self, node_id: &NodeId, network_path: &[NodeId]) { let Some(node_metadata) = self.node_metadata(node_id, network_path) else { log::error!("Could not get nested node_metadata in node_click_targets"); return; @@ -984,16 +923,35 @@ impl NodeNetworkInterface { }; } - fn try_get_node_click_targets(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeClickTargets> { - let node_metadata = self.node_metadata(node_id, network_path)?; - let TransientMetadata::Loaded(click_target) = &node_metadata.transient_metadata.click_targets else { - log::error!("Could not load node type metadata when getting click targets"); - return None; - }; - Some(click_target) + /// Loads the node click targets if needed, then reads them through &self. + pub(crate) fn with_loaded_node_click_targets(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&DocumentNodeClickTargets) -> R) -> Option { + self.try_load_node_click_targets(node_id, network_path); + self.with_node_click_targets(node_id, network_path, read) } - pub fn load_node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) { + /// Reads the modify import/export click targets through &self, loading them first if needed. + pub(crate) fn with_modify_import_export(&self, network_path: &[NodeId], read: impl FnOnce(&ModifyImportExportClickTarget) -> R) -> Option { + let Some(network_metadata) = self.network_metadata(network_path) else { + log::error!("Could not get nested network_metadata in modify_import_export"); + return None; + }; + if !network_metadata.transient_metadata.modify_import_export.is_loaded() { + self.load_modify_import_export(network_path); + } + self.network_metadata(network_path)?.transient_metadata.modify_import_export.with_loaded(read) + } + + /// Reads the node click targets through &self if they are already loaded. + pub(crate) fn with_node_click_targets(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&DocumentNodeClickTargets) -> R) -> Option { + let node_metadata = self.node_metadata(node_id, network_path)?; + let result = node_metadata.transient_metadata.click_targets.with_loaded(read); + if result.is_none() { + log::error!("Could not load node type metadata when getting click targets"); + } + result + } + + pub fn load_node_click_targets(&self, node_id: &NodeId, network_path: &[NodeId]) { let Some(node_position) = self.position_from_downstream_node(node_id, network_path) else { log::error!("Could not get node position in load_node_click_targets for node {node_id}"); return; @@ -1157,24 +1115,24 @@ impl NodeNetworkInterface { } }; - let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { + let Some(node_metadata) = self.node_metadata(node_id, network_path) else { log::error!("Could not get nested node_metadata in load_node_click_targets"); return; }; - node_metadata.transient_metadata.click_targets = TransientMetadata::Loaded(document_node_click_targets); + node_metadata.transient_metadata.click_targets.store(document_node_click_targets); } - pub fn node_bounding_box(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<[DVec2; 2]> { - self.node_click_targets(node_id, network_path) - .and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box()) + pub fn node_bounding_box(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<[DVec2; 2]> { + self.try_load_node_click_targets(node_id, network_path); + self.try_get_node_bounding_box(node_id, network_path) } pub fn try_get_node_bounding_box(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<[DVec2; 2]> { - self.try_get_node_click_targets(node_id, network_path) - .and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box()) + self.with_node_click_targets(node_id, network_path, |click_targets| click_targets.node_click_target.bounding_box()) + .flatten() } - pub fn try_load_all_node_click_targets(&mut self, network_path: &[NodeId]) { + pub fn try_load_all_node_click_targets(&self, network_path: &[NodeId]) { let Some(network) = self.nested_network(network_path) else { log::error!("Could not get network in load_all_node_click_targets"); return; @@ -1185,7 +1143,7 @@ impl NodeNetworkInterface { } /// Get the top left position in node graph coordinates for a node by recursively iterating downstream through cached positions, which means the iteration can be broken once a known position is reached. - pub fn position_from_downstream_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { + pub fn position_from_downstream_node(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { let Some(node_metadata) = self.node_metadata(node_id, network_path) else { log::error!("Could not get nested node_metadata in position_from_downstream_node"); return None; @@ -1196,9 +1154,8 @@ impl NodeNetworkInterface { LayerPosition::Absolute(position) => Some(position), LayerPosition::Stack(y_offset) => { let Some(downstream_node_connectors) = self - .outward_wires(network_path) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0))) - .cloned() + .with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0)).cloned()) + .flatten() else { log::error!("Could not get downstream node in position_from_downstream_node"); return None; @@ -1231,9 +1188,8 @@ impl NodeNetworkInterface { loop { // TODO: Use root node to restore if previewing let Some(downstream_node_connectors) = self - .outward_wires(network_path) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(current_node_id, 0))) - .cloned() + .with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::node(current_node_id, 0)).cloned()) + .flatten() else { log::error!("Could not get downstream node for node {node_id} with Position::Chain"); return None; diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs index 5e824e2ad7..20927b20b0 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs @@ -72,7 +72,6 @@ impl From for DocumentNodePersisten input_name: properties_row.input_name, input_description: properties_row.input_description, }, - ..Default::default() }) } DocumentNodePersistentMetadataHasPrimaryOutput { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/hit_tests.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/hit_tests.rs index fb0e1251be..b829fa54f5 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/hit_tests.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/hit_tests.rs @@ -22,7 +22,7 @@ impl NodeNetworkInterface { all_selected_nodes } - pub fn collect_frontend_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets { + pub fn collect_frontend_click_targets(&self, network_path: &[NodeId]) -> FrontendClickTargets { let mut all_node_click_targets = Vec::new(); let mut connector_click_targets = Vec::new(); let mut icon_click_targets = Vec::new(); @@ -31,15 +31,15 @@ impl NodeNetworkInterface { return FrontendClickTargets::default(); }; let nodes = network_metadata.persistent_metadata.node_metadata.keys().copied().collect::>(); - if let Some(import_export_click_targets) = self.import_export_ports(network_path).cloned() { + self.with_import_export_ports(network_path, |import_export_click_targets| { for port in import_export_click_targets.click_targets() { if let ClickTargetType::Subpath(subpath) = port.target_type() { connector_click_targets.push(subpath.to_bezpath().to_svg()); } } - } + }); nodes.into_iter().for_each(|node_id| { - if let Some(node_click_targets) = self.node_click_targets(&node_id, network_path) { + self.with_loaded_node_click_targets(&node_id, network_path, |node_click_targets| { let mut node_path = String::new(); if let ClickTargetType::Subpath(subpath) = node_click_targets.node_click_target.target_type() { @@ -67,7 +67,7 @@ impl NodeNetworkInterface { icon_click_targets.push(subpath.to_bezpath().to_svg()); } } - } + }); }); let mut layer_click_targets = Vec::new(); let mut node_click_targets = Vec::new(); @@ -79,12 +79,12 @@ impl NodeNetworkInterface { } }); - let bounds = self.all_nodes_bounding_box(network_path).cloned().unwrap_or([DVec2::ZERO, DVec2::ZERO]); + let bounds = self.all_nodes_bounding_box(network_path).unwrap_or([DVec2::ZERO, DVec2::ZERO]); let rect = Subpath::::new_rectangle(bounds[0], bounds[1]); let all_nodes_bounding_box = rect.to_bezpath().to_svg(); let mut modify_import_export = Vec::new(); - if let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) { + self.with_modify_import_export(network_path, |modify_import_export_click_targets| { for click_target in modify_import_export_click_targets .remove_imports_exports .click_targets() @@ -94,7 +94,7 @@ impl NodeNetworkInterface { modify_import_export.push(subpath.to_bezpath().to_svg()); } } - } + }); FrontendClickTargets { node_click_targets, layer_click_targets, @@ -131,7 +131,7 @@ impl NodeNetworkInterface { // TODO: Optimize getting click target intersections from click by using a spacial data structure like a quadtree instead of linear search /// Click target getter methods - pub fn node_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option { + pub fn node_from_click(&self, click: DVec2, network_path: &[NodeId]) -> Option { let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in node_from_click"); return None; @@ -146,8 +146,9 @@ impl NodeNetworkInterface { let clicked_nodes = nodes .iter() .filter(|node_id| { - self.node_click_targets(node_id, network_path) - .is_some_and(|transient_node_metadata| transient_node_metadata.node_click_target.intersect_point_no_stroke(point)) + self.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| { + transient_node_metadata.node_click_target.intersect_point_no_stroke(point) + }) == Some(true) }) .cloned() .collect::>(); @@ -164,7 +165,7 @@ impl NodeNetworkInterface { .or_else(|| clicked_nodes.into_iter().next()) } - pub fn layer_click_target_from_click(&mut self, click: DVec2, click_target_type: LayerClickTargetTypes, network_path: &[NodeId]) -> Option { + pub fn layer_click_target_from_click(&self, click: DVec2, click_target_type: LayerClickTargetTypes, network_path: &[NodeId]) -> Option { let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in visibility_from_click"); return None; @@ -180,7 +181,7 @@ impl NodeNetworkInterface { node_ids .iter() .filter_map(|node_id| { - self.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| { + self.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| { if let NodeTypeClickTargets::Layer(layer) = &transient_node_metadata.node_type_metadata { match click_target_type { LayerClickTargetTypes::Visibility => layer.visibility_click_target.intersect_point_no_stroke(point).then_some(*node_id), @@ -192,11 +193,12 @@ impl NodeNetworkInterface { None } }) + .flatten() }) .next() } - pub fn input_connector_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option { + pub fn input_connector_from_click(&self, click: DVec2, network_path: &[NodeId]) -> Option { let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in input_connector_from_click"); return None; @@ -214,21 +216,22 @@ impl NodeNetworkInterface { .collect::>() .iter() .filter_map(|node_id| { - self.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| { + self.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| { transient_node_metadata .port_click_targets .clicked_input_port_from_point(point) .map(|port| InputConnector::node(*node_id, port)) }) + .flatten() }) .next() .or_else(|| { - self.import_export_ports(network_path) - .and_then(|import_export_ports| import_export_ports.clicked_input_port_from_point(point).map(InputConnector::Export)) + self.with_import_export_ports(network_path, |import_export_ports| import_export_ports.clicked_input_port_from_point(point).map(InputConnector::Export)) + .flatten() }) } - pub fn output_connector_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option { + pub fn output_connector_from_click(&self, click: DVec2, network_path: &[NodeId]) -> Option { let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in output_connector_from_click"); return None; @@ -243,44 +246,51 @@ impl NodeNetworkInterface { nodes .iter() .filter_map(|node_id| { - self.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| { + self.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| { transient_node_metadata .port_click_targets .clicked_output_port_from_point(point) .map(|output_index| OutputConnector::node(*node_id, output_index)) }) + .flatten() }) .next() .or_else(|| { - self.import_export_ports(network_path) - .and_then(|import_export_ports| import_export_ports.clicked_output_port_from_point(point).map(OutputConnector::Import)) + self.with_import_export_ports(network_path, |import_export_ports| { + import_export_ports.clicked_output_port_from_point(point).map(OutputConnector::Import) + }) + .flatten() }) } - pub fn input_position(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { + pub fn input_position(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { match input_connector { InputConnector::Node { node_id, input_index } => self - .node_click_targets(node_id, network_path) - .and_then(|transient_node_metadata| transient_node_metadata.port_click_targets.input_port_position(*input_index)), + .with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| { + transient_node_metadata.port_click_targets.input_port_position(*input_index) + }) + .flatten(), InputConnector::Export(export_index) => self - .import_export_ports(network_path) - .and_then(|import_export_ports| import_export_ports.input_port_position(*export_index)), + .with_import_export_ports(network_path, |import_export_ports| import_export_ports.input_port_position(*export_index)) + .flatten(), } } - pub fn output_position(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option { + pub fn output_position(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option { match output_connector { OutputConnector::Node { node_id, output_index } => self - .node_click_targets(node_id, network_path) - .and_then(|transient_node_metadata| transient_node_metadata.port_click_targets.output_port_position(*output_index)), + .with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| { + transient_node_metadata.port_click_targets.output_port_position(*output_index) + }) + .flatten(), OutputConnector::Import(import_index) => self - .import_export_ports(network_path) - .and_then(|import_export_ports| import_export_ports.output_port_position(*import_index)), + .with_import_export_ports(network_path, |import_export_ports| import_export_ports.output_port_position(*import_index)) + .flatten(), } } /// Get the combined bounding box of the click targets of the selected nodes in the node graph in viewport space - pub fn selected_nodes_bounding_box_viewport(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { + pub fn selected_nodes_bounding_box_viewport(&self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { // Always get the bounding box for nodes in the currently viewed network let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in selected_nodes_bounding_box_viewport"); @@ -310,7 +320,7 @@ impl NodeNetworkInterface { } /// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space - pub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { + pub fn selected_nodes_bounding_box(&self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else { log::error!("Could not get selected nodes in selected_nodes_bounding_box_viewport"); return None; @@ -320,16 +330,13 @@ impl NodeNetworkInterface { .cloned() .collect::>() .iter() - .filter_map(|node_id| { - self.node_click_targets(node_id, network_path) - .and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box()) - }) + .filter_map(|node_id| self.node_bounding_box(node_id, network_path)) .reduce(graphene_std::renderer::Quad::combine_bounds) } /// Gets the bounding box in viewport coordinates for each node in the node graph - pub fn graph_bounds_viewport_space(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { - let bounds = *self.all_nodes_bounding_box(network_path)?; + pub fn graph_bounds_viewport_space(&self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { + let bounds = self.all_nodes_bounding_box(network_path)?; let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in graph_bounds_viewport_space"); return None; @@ -339,7 +346,7 @@ impl NodeNetworkInterface { bounding_box_subpath.bounding_box_with_transform(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport) } - pub fn collect_layer_widths(&mut self, network_path: &[NodeId]) -> (HashMap, HashMap, HashMap) { + pub fn collect_layer_widths(&self, network_path: &[NodeId]) -> (HashMap, HashMap, HashMap) { let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in collect_layer_widths"); return (HashMap::new(), HashMap::new(), HashMap::new()); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/layout.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/layout.rs index 12e7c23015..6bd5eb31f8 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/layout.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/layout.rs @@ -117,7 +117,7 @@ impl NodeNetworkInterface { // } } - pub(crate) fn valid_upstream_chain_nodes(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { + pub(crate) fn valid_upstream_chain_nodes(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { let InputConnector::Node { node_id: input_connector_node_id, input_index, @@ -136,11 +136,16 @@ impl NodeNetworkInterface { if self.is_layer(&upstream_node, network_path) || self.hidden_primary_output(&upstream_node, network_path) { break; } - let Some(outward_wires) = self.outward_wires(network_path).and_then(|outward_wires| outward_wires.get(&OutputConnector::node(upstream_node, 0))) else { + let downstream_connection_count = self + .with_outward_wires(network_path, |outward_wires| { + outward_wires.get(&OutputConnector::node(upstream_node, 0)).map(|connections| connections.len()) + }) + .flatten(); + let Some(downstream_connection_count) = downstream_connection_count else { log::error!("Could not get outward wires in try_set_upstream_to_chain"); break; }; - if outward_wires.len() != 1 { + if downstream_connection_count != 1 { break; } let downstream_position = self.position(&downstream_id, network_path); @@ -300,13 +305,15 @@ impl NodeNetworkInterface { return; }; if !shift_without_push { + // The owned nodes of each layer are populated by the stack dependents load, which otherwise may not run until after this filter + self.try_load_stack_dependents(network_path); for node_id in node_ids.clone() { - if self.is_layer(&node_id, network_path) - && let Some(owned_nodes) = self.owned_nodes(&node_id, network_path) - { - for owned_node in owned_nodes { - node_ids.remove(owned_node); - } + if self.is_layer(&node_id, network_path) { + self.with_owned_nodes(&node_id, network_path, |owned_nodes| { + for owned_node in owned_nodes { + node_ids.remove(owned_node); + } + }); }; } } @@ -429,7 +436,7 @@ impl NodeNetworkInterface { log::error!("Could not get nested network_metadata in export_ports"); continue; }; - if let TransientMetadata::Loaded(stack_dependents) = &mut network_metadata.transient_metadata.stack_dependents + if let Some(stack_dependents) = network_metadata.transient_metadata.stack_dependents.get_loaded_mut() && let Some(LayerOwner::None(offset)) = stack_dependents.get_mut(node_id) { *offset += shift_sign; @@ -472,7 +479,7 @@ impl NodeNetworkInterface { if self.selected_nodes_in_nested_network(network_path).is_some_and(|selected_nodes| { selected_nodes .selected_nodes() - .any(|selected_node| selected_node == node_id || self.owned_nodes(node_id, network_path).is_some_and(|owned_nodes| owned_nodes.contains(selected_node))) + .any(|selected_node| selected_node == node_id || self.with_owned_nodes(node_id, network_path, |owned_nodes| owned_nodes.contains(selected_node)) == Some(true)) }) { return None; }; @@ -530,7 +537,7 @@ impl NodeNetworkInterface { log::error!("Could not get nested network_metadata in export_ports"); return; }; - let TransientMetadata::Loaded(stack_dependents) = &mut network_metadata.transient_metadata.stack_dependents else { + let Some(stack_dependents) = network_metadata.transient_metadata.stack_dependents.get_loaded_mut() else { log::error!("Stack dependents should be loaded in vertical_shift_with_push"); return; }; @@ -564,7 +571,7 @@ impl NodeNetworkInterface { } // Shift the nodes that are owned by the layer (if any) - if let Some(owned_nodes) = self.owned_nodes(node_id, network_path).cloned() { + if let Some(owned_nodes) = self.with_owned_nodes(node_id, network_path, |owned_nodes| owned_nodes.clone()) { for owned_node in owned_nodes { if self.is_absolute(&owned_node, network_path) { self.try_shift_node(&owned_node, IVec2::new(0, shift_sign), shifted_nodes, network_path); @@ -576,53 +583,58 @@ impl NodeNetworkInterface { pub(crate) fn check_collision_with_stack_dependents(&mut self, node_id: &NodeId, shift_sign: i32, network_path: &[NodeId]) -> Vec<(NodeId, LayerOwner)> { self.try_load_all_node_click_targets(network_path); self.try_load_stack_dependents(network_path); - let Some(stack_dependents) = self.try_get_stack_dependents(network_path) else { + + // Check collisions and for all owned nodes and recursively shift them + let nodes_to_shift = self.with_stack_dependents(network_path, |stack_dependents| { + let mut nodes_to_shift = Vec::new(); + + let owned_nodes = self.with_owned_nodes(node_id, network_path, |owned_nodes| owned_nodes.clone()).unwrap_or_default(); + + for current_node in owned_nodes.iter().chain(std::iter::once(node_id)) { + for node_to_check_collision in stack_dependents { + // Do not check collision between any of the owned nodes or the shifted node + if owned_nodes.contains(node_to_check_collision.0) || node_to_check_collision.0 == node_id { + continue; + } + + if node_to_check_collision.0 == current_node { + continue; + } + let Some(mut current_node_bounding_box) = self.try_get_node_bounding_box(current_node, network_path) else { + log::error!("Could not get bounding box for node {node_id} in shift_selected_nodes"); + continue; + }; + + let Some(node_bounding_box) = self.try_get_node_bounding_box(node_to_check_collision.0, network_path) else { + log::error!("Could not get bounding box for node {node_to_check_collision:?} in shift_selected_nodes"); + continue; + }; + // If the nodes do not intersect horizontally, then there is no collision + if current_node_bounding_box[1].x < node_bounding_box[0].x || current_node_bounding_box[0].x > node_bounding_box[1].x { + continue; + } + // Do not check collision if the nodes are currently intersecting + if current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1 { + continue; + } + + current_node_bounding_box[1].y += GRID_SIZE as f64 * shift_sign as f64; + current_node_bounding_box[0].y += GRID_SIZE as f64 * shift_sign as f64; + + let collision = current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1; + if collision { + nodes_to_shift.push((*node_to_check_collision.0, node_to_check_collision.1.clone())); + } + } + } + + nodes_to_shift + }); + + let Some(nodes_to_shift) = nodes_to_shift else { log::error!("Could not load stack dependents in shift_selected_nodes"); return Vec::new(); }; - // Check collisions and for all owned nodes and recursively shift them - let mut nodes_to_shift = Vec::new(); - - let default_hashset = HashSet::new(); - let owned_nodes = self.owned_nodes(node_id, network_path).unwrap_or(&default_hashset); - - for current_node in owned_nodes.iter().chain(std::iter::once(node_id)) { - for node_to_check_collision in stack_dependents { - // Do not check collision between any of the owned nodes or the shifted node - if owned_nodes.contains(node_to_check_collision.0) || node_to_check_collision.0 == node_id { - continue; - } - - if node_to_check_collision.0 == current_node { - continue; - } - let Some(mut current_node_bounding_box) = self.try_get_node_bounding_box(current_node, network_path) else { - log::error!("Could not get bounding box for node {node_id} in shift_selected_nodes"); - continue; - }; - - let Some(node_bounding_box) = self.try_get_node_bounding_box(node_to_check_collision.0, network_path) else { - log::error!("Could not get bounding box for node {node_to_check_collision:?} in shift_selected_nodes"); - continue; - }; - // If the nodes do not intersect horizontally, then there is no collision - if current_node_bounding_box[1].x < node_bounding_box[0].x || current_node_bounding_box[0].x > node_bounding_box[1].x { - continue; - } - // Do not check collision if the nodes are currently intersecting - if current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1 { - continue; - } - - current_node_bounding_box[1].y += GRID_SIZE as f64 * shift_sign as f64; - current_node_bounding_box[0].y += GRID_SIZE as f64 * shift_sign as f64; - - let collision = current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1; - if collision { - nodes_to_shift.push((*node_to_check_collision.0, node_to_check_collision.1.clone())); - } - } - } nodes_to_shift } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs index caf31d94d8..28b6014396 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs @@ -1486,7 +1486,6 @@ impl NodeNetworkInterface { node_metadata.persistent_metadata.node_type_metadata = if is_layer { NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { position: LayerPosition::Absolute(position), - owned_nodes: TransientMetadata::Unloaded, }) } else { NodeTypePersistentMetadata::Node(NodePersistentMetadata { @@ -1509,15 +1508,11 @@ impl NodeNetworkInterface { if let Some(downstream_position) = is_layer.then_some(single_downstream_layer_position).flatten() { node_metadata.persistent_metadata.node_type_metadata = NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { position: LayerPosition::Stack((position.y - downstream_position.y - STACK_VERTICAL_GAP).max(0) as u32), - owned_nodes: TransientMetadata::Unloaded, }) } - if is_layer { - node_metadata.transient_metadata.node_type_metadata = NodeTypeTransientMetadata::Layer(LayerTransientMetadata::default()); - } else { - node_metadata.transient_metadata.node_type_metadata = NodeTypeTransientMetadata::Node; - } + node_metadata.transient_metadata.layer_width.unload(); + node_metadata.transient_metadata.owned_nodes.unload(); self.transaction_modified(); self.unload_stack_dependents(network_path); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs index c64b25bde2..112b036312 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs @@ -103,27 +103,38 @@ impl NodeNetworkInterface { } /// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself. - pub fn downstream_layer_for_chain_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { + pub fn downstream_layer_for_chain_node(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { let mut id = *node_id; while !self.is_layer(&id, network_path) { - id = self.outward_wires(network_path)?.get(&OutputConnector::node(id, 0))?.first()?.node_id()?; + id = self.with_outward_wires(network_path, |outward_wires| { + outward_wires + .get(&OutputConnector::node(id, 0)) + .and_then(|connections| connections.first()) + .and_then(|connector| connector.node_id()) + })??; } Some(id) } /// Returns all downstream layers (inclusive) from a node. If the node is a layer, it will return itself. - pub fn downstream_layers(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Vec { + pub fn downstream_layers(&self, node_id: &NodeId, network_path: &[NodeId]) -> Vec { let mut stack = vec![*node_id]; let mut layers = Vec::new(); while let Some(current_node) = stack.pop() { if self.is_layer(¤t_node, network_path) { layers.push(current_node); } else { - let Some(outward_wires) = self.outward_wires(network_path).and_then(|outward_wires| outward_wires.get(&OutputConnector::node(current_node, 0))) else { + let downstream_found = self.with_outward_wires(network_path, |outward_wires| { + let Some(connections) = outward_wires.get(&OutputConnector::node(current_node, 0)) else { + return false; + }; + stack.extend(connections.iter().filter_map(|input_connector| input_connector.node_id())); + true + }); + if downstream_found != Some(true) { log::error!("Could not get outward wires in downstream_layer"); return Vec::new(); - }; - stack.extend(outward_wires.iter().filter_map(|input_connector| input_connector.node_id())); + } } } layers @@ -167,18 +178,22 @@ impl NodeNetworkInterface { /// Creates a copy for each node by disconnecting nodes which are not connected to other copied nodes. /// Returns an iterator of all persistent metadata for a node and their ids - pub fn copy_nodes<'a>(&'a mut self, new_ids: &'a HashMap, network_path: &'a [NodeId]) -> impl Iterator + 'a { + pub fn copy_nodes<'a>(&'a self, new_ids: &'a HashMap, network_path: &'a [NodeId]) -> impl Iterator + 'a { let mut new_nodes = new_ids .iter() .filter_map(|(node_id, &new)| { self.create_node_template(node_id, network_path).and_then(|mut node_template| { - let Some(outward_wires) = self.outward_wires(network_path) else { + // TODO: Get downstream connections from all outputs + let Some(has_selected_node_downstream) = self.with_outward_wires(network_path, |outward_wires| { + outward_wires.get(&OutputConnector::node(*node_id, 0)).is_some_and(|outputs| { + outputs + .iter() + .any(|input_connector| input_connector.node_id().is_some_and(|upstream_id| new_ids.keys().any(|key| *key == upstream_id))) + }) + }) else { log::error!("Could not get outward wires in copy_nodes"); return None; }; - // TODO: Get downstream connections from all outputs - let mut downstream_connections = outward_wires.get(&OutputConnector::node(*node_id, 0)).map_or([].iter(), |outputs| outputs.iter()); - let has_selected_node_downstream = downstream_connections.any(|input_connector| input_connector.node_id().is_some_and(|upstream_id| new_ids.keys().any(|key| *key == upstream_id))); // If the copied node does not have a downstream connection to another copied node, then set the position to absolute if !has_selected_node_downstream { let Some(position) = self.position(node_id, network_path) else { @@ -247,7 +262,7 @@ impl NodeNetworkInterface { /// Converts all node id inputs to a new id based on a HashMap. /// /// If the node is not in the hashmap then a default input is found based on the compiled network, using the node_id passed as a parameter - pub fn map_ids(&mut self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap, network_path: &[NodeId]) -> NodeTemplate { + pub fn map_ids(&self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap, network_path: &[NodeId]) -> NodeTemplate { for (input_index, input) in node_template.inputs.iter_mut().enumerate() { if let &mut NodeInput::Node { node_id: id, output_index } = input { if let Some(&new_id) = new_ids.get(&id) { @@ -278,16 +293,14 @@ impl NodeNetworkInterface { } } - pub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { - let top_left_position = self - .node_click_targets(node_id, network_path) - .and_then(|click_targets| click_targets.node_click_target.bounding_box()) - .map(|mut bounding_box| { - if !self.is_layer(node_id, network_path) { - bounding_box[0] -= DVec2::new(0., 12.); - } - (bounding_box[0] / 24.).as_ivec2() - }); + pub fn position(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { + self.try_load_node_click_targets(node_id, network_path); + let top_left_position = self.try_get_node_bounding_box(node_id, network_path).map(|mut bounding_box| { + if !self.is_layer(node_id, network_path) { + bounding_box[0] -= DVec2::new(0., 12.); + } + (bounding_box[0] / 24.).as_ivec2() + }); top_left_position.map(|position| { if self.is_layer(node_id, network_path) { position + IVec2::new(self.chain_width(node_id, network_path) as i32, 0) @@ -301,7 +314,7 @@ impl NodeNetworkInterface { collect_network_resources(self.document_network(), target); } - pub fn frontend_imports(&mut self, network_path: &[NodeId]) -> Vec> { + pub fn frontend_imports(&self, network_path: &[NodeId]) -> Vec> { match network_path.split_last() { Some((node_id, encapsulating_network_path)) => { let Some(node) = self.document_node(node_id, encapsulating_network_path) else { @@ -321,7 +334,7 @@ impl NodeNetworkInterface { } } - pub fn frontend_exports(&mut self, network_path: &[NodeId]) -> Vec> { + pub fn frontend_exports(&self, network_path: &[NodeId]) -> Vec> { let Some(network) = self.nested_network(network_path) else { return Vec::new() }; let mut frontend_exports = ((0..network.exports.len()).map(|export_index| self.frontend_input_from_connector(&InputConnector::Export(export_index), network_path))).collect::>(); if frontend_exports.is_empty() { @@ -330,8 +343,8 @@ impl NodeNetworkInterface { frontend_exports } - pub fn import_export_position(&mut self, network_path: &[NodeId]) -> Option<(IVec2, IVec2)> { - let Some(all_nodes_bounding_box) = self.all_nodes_bounding_box(network_path).cloned() else { + pub fn import_export_position(&self, network_path: &[NodeId]) -> Option<(IVec2, IVec2)> { + let Some(all_nodes_bounding_box) = self.all_nodes_bounding_box(network_path) else { log::error!("Could not get all nodes bounding box in load_export_ports"); return None; }; @@ -408,7 +421,7 @@ impl NodeNetworkInterface { } /// Returns None if there is an error, it is a hidden primary export, or a hidden input - pub fn frontend_input_from_connector(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { + pub fn frontend_input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { // Return None if it is a hidden input if self.input_from_connector(input_connector, network_path).is_some_and(|input| !input.is_exposed()) { return None; @@ -471,7 +484,7 @@ impl NodeNetworkInterface { } /// Returns None if there is an error, it is the document network, a hidden primary output or import - pub fn frontend_output_from_connector(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option { + pub fn frontend_output_from_connector(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option { let output_type = self.output_type(output_connector, network_path); let (name, description) = match output_connector { OutputConnector::Node { node_id, output_index } => { @@ -512,9 +525,8 @@ impl NodeNetworkInterface { let data_type = output_type.displayed_type(); let resolved_type = output_type.resolved_type_node_string(); let mut connected_to = self - .outward_wires(network_path) - .and_then(|outward_wires| outward_wires.get(output_connector)) - .cloned() + .with_outward_wires(network_path, |outward_wires| outward_wires.get(output_connector).cloned()) + .flatten() .unwrap_or_default() .iter() .map(|input| match input { @@ -539,10 +551,10 @@ impl NodeNetworkInterface { }) } - pub fn height_from_click_target(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { + pub fn height_from_click_target(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { + self.try_load_node_click_targets(node_id, network_path); let mut node_height: Option = self - .node_click_targets(node_id, network_path) - .and_then(|click_targets: &DocumentNodeClickTargets| click_targets.node_click_target.bounding_box()) + .try_get_node_bounding_box(node_id, network_path) .map(|bounding_box| ((bounding_box[1].y - bounding_box[0].y) / 24.) as u32); if !self.is_layer(node_id, network_path) { node_height = node_height.map(|height| height + 1); @@ -552,7 +564,7 @@ impl NodeNetworkInterface { /// Returns whether every downstream path from the node's outputs stays within the dependent set defined by `classify`, meaning nothing else in the graph depends on this node. /// Reaching an export or a dead end (a walked node with no outward wires) always escapes. O(nodes + wires) per call. - pub(crate) fn is_sole_dependent(&mut self, node_id: NodeId, network_path: &[NodeId], classify: impl Fn(NodeId, usize) -> SoleDependentStep) -> bool { + pub(crate) fn is_sole_dependent(&self, node_id: NodeId, network_path: &[NodeId], classify: impl Fn(NodeId, usize) -> SoleDependentStep) -> bool { let mut visited = HashSet::new(); let mut stack = vec![node_id]; @@ -561,47 +573,54 @@ impl NodeNetworkInterface { continue; } - let number_of_outputs = self.number_of_outputs(¤t_node, network_path); - let Some(outward_wires) = self.outward_wires(network_path) else { - log::error!("Could not get outward wires in is_sole_dependent"); - return false; - }; - // Classify every downstream connection of this node, collecting the ones to keep walking through - let mut has_downstream_connections = false; - let mut nodes_to_walk_through = Vec::new(); - for output_index in 0..number_of_outputs { - let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(current_node, output_index)) else { - continue; - }; - for downstream_connection in downstream_connections { - has_downstream_connections = true; - let InputConnector::Node { - node_id: downstream_node, - input_index, - } = downstream_connection - else { - return false; + let number_of_outputs = self.number_of_outputs(¤t_node, network_path); + let keeps_within_set = self.with_outward_wires(network_path, |outward_wires| { + let mut has_downstream_connections = false; + let mut nodes_to_walk_through = Vec::new(); + for output_index in 0..number_of_outputs { + let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(current_node, output_index)) else { + continue; }; - match classify(*downstream_node, *input_index) { - SoleDependentStep::Terminate => {} - SoleDependentStep::Continue => nodes_to_walk_through.push(*downstream_node), - SoleDependentStep::Escape => return false, + for downstream_connection in downstream_connections { + has_downstream_connections = true; + let InputConnector::Node { + node_id: downstream_node, + input_index, + } = downstream_connection + else { + return false; + }; + match classify(*downstream_node, *input_index) { + SoleDependentStep::Terminate => {} + SoleDependentStep::Continue => nodes_to_walk_through.push(*downstream_node), + SoleDependentStep::Escape => return false, + } } } - } - if !has_downstream_connections { - return false; + if !has_downstream_connections { + return false; + } + stack.extend(nodes_to_walk_through); + true + }); + + match keeps_within_set { + Some(true) => {} + Some(false) => return false, + None => { + log::error!("Could not get outward wires in is_sole_dependent"); + return false; + } } - stack.extend(nodes_to_walk_through); } true } // All chain nodes and branches from the chain which are sole dependents of the layer - pub fn upstream_nodes_below_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> HashSet { + pub fn upstream_nodes_below_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> HashSet { // Every upstream node below layer must be a sole dependent let mut upstream_nodes_below_layer = HashSet::new(); @@ -712,10 +731,6 @@ impl NodeNetworkInterface { self.view(network_path).ok().and_then(|view| view.persistent_input_metadata(node_id, index).ok()) } - pub(crate) fn transient_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputTransientMetadata> { - self.view(network_path).ok().and_then(|view| view.transient_input_metadata(node_id, index).ok()) - } - pub fn set_input_override(&mut self, node_id: &NodeId, index: usize, widget_override: Option, network_path: &[NodeId]) { let Some(metadata) = self .node_metadata_mut(node_id, network_path) @@ -728,7 +743,7 @@ impl NodeNetworkInterface { } /// Returns the input name to display in the properties panel. If the name is empty then the type is used. - pub fn displayed_input_name_and_description(&mut self, node_id: &NodeId, input_index: usize, network_path: &[NodeId]) -> (String, String) { + pub fn displayed_input_name_and_description(&self, node_id: &NodeId, input_index: usize, network_path: &[NodeId]) -> (String, String) { let Some(input_metadata) = self.persistent_input_metadata(node_id, input_index, network_path) else { log::warn!("input metadata not found in displayed_input_name_and_description"); return (String::new(), String::new()); @@ -775,12 +790,12 @@ impl NodeNetworkInterface { self.query(network_path, "is_layer", |view| view.is_layer(node_id)).unwrap_or_default() } - pub fn primary_output_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(outward_wires) = self.outward_wires(network_path) else { + pub fn primary_output_connected_to_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { + let Some(downstream_connectors) = self.with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0)).cloned()) else { log::error!("Could not get outward_wires in primary_output_connected_to_layer"); return false; }; - let Some(downstream_connectors) = outward_wires.get(&OutputConnector::node(*node_id, 0)) else { + let Some(downstream_connectors) = downstream_connectors else { log::error!("Could not get downstream_connectors in primary_output_connected_to_layer"); return false; }; @@ -1086,7 +1101,6 @@ impl NodeNetworkInterface { node_metadata.persistent_metadata.node_type_metadata = if old_node.is_layer { NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { position: LayerPosition::Absolute(old_node.metadata.position), - owned_nodes: TransientMetadata::Unloaded, }) } else { NodeTypePersistentMetadata::Node(NodePersistentMetadata { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index e023c98fd4..ef2567b987 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -116,7 +116,7 @@ impl TypeSource { } impl NodeNetworkInterface { - fn input_has_error(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> bool { + fn input_has_error(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> bool { match input_connector { InputConnector::Node { node_id, input_index } => { let Some(implementation) = self.implementation(node_id, network_path) else { @@ -126,9 +126,9 @@ impl NodeNetworkInterface { let node_path = [network_path, &[*node_id]].concat(); match implementation { DocumentNodeImplementation::Network(_) => { - let Some(map) = self.outward_wires(&node_path) else { return false }; - let Some(outward_wires) = map.get(&OutputConnector::Import(*input_index)) else { return false }; - outward_wires.clone().iter().any(|connector| match connector { + let outward_wires = self.with_outward_wires(&node_path, |map| map.get(&OutputConnector::Import(*input_index)).cloned()).flatten(); + let Some(outward_wires) = outward_wires else { return false }; + outward_wires.iter().any(|connector| match connector { InputConnector::Node { node_id, input_index } => self.input_has_error(&InputConnector::node(*node_id, *input_index), &node_path), InputConnector::Export(_) => false, }) @@ -148,7 +148,7 @@ impl NodeNetworkInterface { } } - pub fn input_type_not_invalid(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TypeSource { + pub fn input_type_not_invalid(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> TypeSource { let Some(input) = self.input_from_connector(input_connector, network_path) else { return TypeSource::Error("Could not get input from connector"); }; @@ -176,7 +176,7 @@ impl NodeNetworkInterface { /// Get the [`TypeSource`] for any InputConnector. /// If the input is not compiled, then an Unknown or default from the definition is returned. - pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TypeSource { + pub fn input_type(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> TypeSource { // First check if there is an error with this node or any protonodes it is connected to if self.input_has_error(input_connector, network_path) { return TypeSource::Invalid; @@ -185,7 +185,7 @@ impl NodeNetworkInterface { } /// Gets the default tagged value for an input. If its not compiled, then it tries to get a valid type. If there are no valid types, then it picks a random implementation. - pub fn tagged_value_from_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TaggedValue { + pub fn tagged_value_from_input(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> TaggedValue { let guaranteed_type = match self.input_type(input_connector, network_path) { TypeSource::Compiled(compiled) => compiled, TypeSource::TaggedValue(value) => value, @@ -213,7 +213,7 @@ impl NodeNetworkInterface { } /// A list of all valid input types for this specific node. - pub fn potential_valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { + pub fn potential_valid_input_types(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { let InputConnector::Node { node_id, input_index } = input_connector else { // An export can have any type connected to it return vec![graph_craft::generic!(T)]; @@ -225,17 +225,15 @@ impl NodeNetworkInterface { match implementation { DocumentNodeImplementation::Network(_) => { let nested_path = [network_path, &[*node_id]].concat(); - let Some(outward_wires) = self.outward_wires(&nested_path) else { - log::error!("Could not get outward wires in potential_valid_input_types"); - return Vec::new(); - }; - let Some(inputs_from_import) = outward_wires.get(&OutputConnector::Import(*input_index)) else { + let inputs_from_import = self + .with_outward_wires(&nested_path, |outward_wires| outward_wires.get(&OutputConnector::Import(*input_index)).cloned()) + .flatten(); + let Some(inputs_from_import) = inputs_from_import else { log::error!("Could not get inputs from import in potential_valid_input_types"); return Vec::new(); }; let intersection: HashSet = inputs_from_import - .clone() .iter() .map(|input_connector| self.potential_valid_input_types(input_connector, &nested_path).into_iter().collect::>()) .fold(None, |acc: Option>, set| match acc { @@ -280,7 +278,7 @@ impl NodeNetworkInterface { } /// Performs a downstream traversal to ensure input type will work in the full context of the graph. - pub fn complete_valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { + pub fn complete_valid_input_types(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { match input_connector { InputConnector::Node { node_id, input_index } => { let Some(implementation) = self.implementation(node_id, network_path) else { @@ -335,7 +333,7 @@ impl NodeNetworkInterface { } } - pub fn output_type(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> TypeSource { + pub fn output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> TypeSource { match output_connector { OutputConnector::Node { node_id, output_index } => { // A hidden node is replaced by a passthrough during flattening, so its output carries its primary input's type @@ -372,18 +370,14 @@ impl NodeNetworkInterface { } /// The valid output types are all types that are valid for each downstream connection. - fn valid_output_types(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Vec { - let Some(outward_wires) = self.outward_wires(network_path) else { - log::error!("Could not get outward wires in valid_output_types"); - return Vec::new(); - }; - let Some(inputs_from_import) = outward_wires.get(output_connector) else { + fn valid_output_types(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Vec { + let inputs_from_import = self.with_outward_wires(network_path, |outward_wires| outward_wires.get(output_connector).cloned()).flatten(); + let Some(inputs_from_import) = inputs_from_import else { log::error!("Could not get inputs from import in valid_output_types"); return Vec::new(); }; let intersection = inputs_from_import - .clone() .iter() .map(|input_connector| self.potential_valid_input_types(input_connector, network_path).into_iter().collect::>()) .fold(None, |acc: Option>, set| match acc { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/storage_metadata.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/storage_metadata.rs index 7085cd8d6e..cb46371cb6 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/storage_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/storage_metadata.rs @@ -387,6 +387,5 @@ fn input_metadata_entry_to_runtime(entry: InputMetadataEntry) -> InputMetadata { widget_override: entry.widget_override, input_data: entry.input_data, }, - ..Default::default() } } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs index a3910da030..d7dc8f1bd3 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs @@ -377,26 +377,66 @@ impl TransientMetadata { } } +/// A lazily computed cache slot whose load and read paths work through &self, with interior mutability guarding the stored value. +#[derive(Debug, Clone)] +pub(crate) struct TransientCache(std::cell::RefCell>); + +impl Default for TransientCache { + fn default() -> Self { + TransientCache(std::cell::RefCell::new(TransientMetadata::Unloaded)) + } +} + +impl TransientCache { + pub(crate) fn is_loaded(&self) -> bool { + self.0.borrow().is_loaded() + } + + pub(crate) fn store(&self, value: T) { + *self.0.borrow_mut() = TransientMetadata::Loaded(value); + } + + pub(crate) fn unload(&self) { + *self.0.borrow_mut() = TransientMetadata::Unloaded; + } + + /// Runs `read` on the cached value if it is loaded. + pub(crate) fn with_loaded(&self, read: impl FnOnce(&T) -> R) -> Option { + match &*self.0.borrow() { + TransientMetadata::Loaded(value) => Some(read(value)), + TransientMetadata::Unloaded => None, + } + } + + /// Direct access without runtime borrow tracking, for callers already holding exclusive access. + pub(crate) fn get_loaded_mut(&mut self) -> Option<&mut T> { + match self.0.get_mut() { + TransientMetadata::Loaded(value) => Some(value), + TransientMetadata::Unloaded => None, + } + } +} + /// If some network calculation is too slow to compute for every usage, cache the data here #[derive(Debug, Default, Clone)] pub struct NodeNetworkTransientMetadata { pub selected_nodes: SelectedNodes, /// Sole dependents of the top of the stacks of all selected nodes. Used to determine which nodes are checked for collision when shifting. /// The LayerOwner is used to determine whether the collided node should be shifted, or the layer that owns it. - pub stack_dependents: TransientMetadata>, + pub(crate) stack_dependents: TransientCache>, /// Cache for the bounding box around all nodes in node graph space. - pub all_nodes_bounding_box: TransientMetadata<[DVec2; 2]>, + pub(crate) all_nodes_bounding_box: TransientCache<[DVec2; 2]>, // /// Cache bounding box for all "groups of nodes", which will be used to prevent overlapping nodes // node_group_bounding_box: Vec<(Subpath, Vec)>, /// Cache for all outward wire connections - pub outward_wires: TransientMetadata>>, + pub(crate) outward_wires: TransientCache>>, /// All export connector click targets - pub import_export_ports: TransientMetadata, + pub(crate) import_export_ports: TransientCache, /// Click targets for adding, removing, and moving import/export ports - pub modify_import_export: TransientMetadata, + pub(crate) modify_import_export: TransientCache, - // Wires from the exports - pub wires: Vec>, + /// Cached wire SVG paths per input connector, where an entry's presence means that wire is loaded. + pub(crate) wires: std::cell::RefCell>, } #[derive(Debug, Clone)] @@ -576,13 +616,6 @@ impl InputPersistentMetadata { } } -#[derive(Debug, Clone, Default)] -pub(crate) struct InputTransientMetadata { - pub(crate) wire: TransientMetadata, - // downstream_protonode: populated for all inputs after each compile - // types: populated for each protonode after each -} - /// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node. #[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] @@ -613,20 +646,9 @@ impl DocumentNodePersistentMetadata { } } -#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct InputMetadata { pub persistent_metadata: InputPersistentMetadata, - #[serde(skip)] - pub(crate) transient_metadata: InputTransientMetadata, -} - -impl Clone for InputMetadata { - fn clone(&self) -> Self { - InputMetadata { - persistent_metadata: self.persistent_metadata.clone(), - transient_metadata: Default::default(), - } - } } impl PartialEq for InputMetadata { @@ -641,7 +663,6 @@ impl From<(&str, &str)> for InputMetadata { persistent_metadata: InputPersistentMetadata::default() .with_name(input_name_and_description.0) .with_description(input_name_and_description.1), - ..Default::default() } } } @@ -650,7 +671,6 @@ impl InputMetadata { pub fn with_name_description_override(input_name: &str, description: &str, widget_override: WidgetOverride) -> Self { InputMetadata { persistent_metadata: InputPersistentMetadata::default().with_name(input_name).with_description(description).with_override(widget_override), - ..Default::default() } } } @@ -676,7 +696,6 @@ impl NodeTypePersistentMetadata { pub fn layer(position: IVec2) -> NodeTypePersistentMetadata { NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { position: LayerPosition::Absolute(position), - owned_nodes: TransientMetadata::default(), }) } } @@ -688,9 +707,6 @@ pub struct LayerPersistentMetadata { // preview_click_target: Option, /// Stores the position of a layer node, which can either be Absolute or Stack pub position: LayerPosition, - /// All nodes that should be moved when the layer is moved. - #[serde(skip)] - pub owned_nodes: TransientMetadata>, } impl PartialEq for LayerPersistentMetadata { @@ -736,9 +752,11 @@ pub enum NodePosition { #[derive(Debug, Default, Clone)] pub struct DocumentNodeTransientMetadata { // The click targets are stored as a single struct since it is very rare for only one to be updated, and recomputing all click targets in one function is more efficient than storing them separately. - pub click_targets: TransientMetadata, - // Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer. - pub node_type_metadata: NodeTypeTransientMetadata, + pub(crate) click_targets: TransientCache, + /// All nodes that should be moved when this layer is moved, kept here since only layers own nodes. + pub(crate) owned_nodes: TransientCache>, + /// Width in grid units from the left edge of the layer's thumbnail to its left end, cached since text measurement is slow. Only loaded for layers. + pub(crate) layer_width: TransientCache, } #[derive(Debug, Clone)] @@ -752,23 +770,6 @@ pub struct DocumentNodeClickTargets { pub node_type_metadata: NodeTypeClickTargets, } -#[derive(Debug, Default, Clone)] -pub enum NodeTypeTransientMetadata { - Layer(LayerTransientMetadata), - #[default] - Node, // No transient data is stored exclusively for nodes -} - -#[derive(Debug, Default, Clone)] -pub struct LayerTransientMetadata { - // Stores the width in grid units for layer nodes from the left edge of the thumbnail (+12px padding since thumbnail ends between grid spaces) to the left end of the node - /// This is necessary since calculating the layer width through web_sys is very slow - pub layer_width: TransientMetadata, - // Should not be a performance concern to calculate when needed with chain_width. - // Stores the width in grid units for layer nodes from the left edge of the thumbnail to the end of the chain - // chain_width: u32, -} - #[derive(Debug, Clone)] pub enum NodeTypeClickTargets { Layer(Box), diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs index 82ad13a0e7..3fb723d64d 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs @@ -290,14 +290,6 @@ impl<'a, 'p> NetworkView<'a, 'p> { Ok(&input_metadata.persistent_metadata) } - pub(crate) fn transient_input_metadata(&self, node_id: &NodeId, index: usize) -> Result<&'a InputTransientMetadata, NetworkError> { - let metadata = self.node_metadata(node_id)?; - let input_metadata = metadata.persistent_metadata.input_metadata.get(index).ok_or(NetworkError::InputNotFound { - connector: InputConnector::node(*node_id, index), - })?; - Ok(&input_metadata.transient_metadata) - } - pub fn upstream_output_connector(&self, input_connector: &InputConnector) -> Result, NetworkError> { Ok(match self.input(input_connector)? { NodeInput::Node { node_id, output_index, .. } => Some(OutputConnector::node(*node_id, *output_index)),