diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 75f5a15ea0..f1800fcb8d 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -13,8 +13,10 @@ mod structure; mod types; #[cfg(test)] mod validation; +mod view; pub use types::*; +pub use view::{NetworkError, NetworkView}; use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier, NodeRelations}; use super::misc::PTZ; 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 3f557e471b..fb0e1251be 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 @@ -110,15 +110,7 @@ impl NodeNetworkInterface { } pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(node) = self.document_node(node_id, network_path) else { - log::error!("Could not get node {node_id} in is_eligible_to_be_layer"); - return false; - }; - let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count(); - let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed()); - let output_count = self.number_of_outputs(node_id, network_path); - - !self.hidden_primary_output(node_id, network_path) && output_count == 1 && (input_count <= 2) && parameters_hidden + self.query(network_path, "is_eligible_to_be_layer", |view| view.is_eligible_to_be_layer(node_id)).unwrap_or_default() } pub fn node_graph_ptz(&self, network_path: &[NodeId]) -> Option<&PTZ> { 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 13f2a860fd..ef73cd3da8 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 @@ -1,5 +1,18 @@ use super::*; +impl NodeNetworkInterface { + /// Runs a query against a resolved [`NetworkView`], logging any error at this message-boundary wrapper and mapping it to None. + pub(crate) fn query<'a, 'p, T>(&'a self, network_path: &'p [NodeId], caller: &str, query: impl FnOnce(NetworkView<'a, 'p>) -> Result) -> Option { + match self.view(network_path).and_then(query) { + Ok(value) => Some(value), + Err(error) => { + log::error!("{error} in {caller}"); + None + } + } + } +} + // Public immutable getters for the network interface impl NodeNetworkInterface { /// Gets the network of the root document @@ -25,21 +38,11 @@ impl NodeNetworkInterface { /// Get the specified document node in the nested network based on node_id and network_path pub fn document_node(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNode> { - let network = self.nested_network(network_path)?; - let Some(node_metadata) = network.nodes.get(node_id) else { - log::error!("Could not get document node with id {node_id} in network {network_path:?}"); - return None; - }; - Some(node_metadata) + self.query(network_path, "document_node", |view| view.node(node_id)) } pub fn node_metadata(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> { - let network_metadata = self.network_metadata(network_path)?; - let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(node_id) else { - log::error!("Could not get node metadata for node {node_id} in network {network_path:?}"); - return None; - }; - Some(node_metadata) + self.query(network_path, "node_metadata", |view| view.node_metadata(node_id)) } pub fn document_network_metadata(&self) -> &NodeNetworkMetadata { @@ -69,57 +72,34 @@ impl NodeNetworkInterface { /// Get the selected nodes for the network at the network_path pub fn selected_nodes_in_nested_network(&self, network_path: &[NodeId]) -> Option { - let Some(network_metadata) = self.network_metadata(network_path) else { - log::error!("Could not get nested network_metadata in selected_nodes"); - return None; - }; + self.query(network_path, "selected_nodes_in_nested_network", |view| Ok(view.selected_nodes())) + } - Some( - network_metadata - .persistent_metadata - .selection_undo_history - .back() - .cloned() - .unwrap_or_default() - .filtered_selected_nodes(|node_id| network_metadata.persistent_metadata.node_metadata.contains_key(node_id)), - ) + /// Runs an encapsulating-node query, staying silent for the document network which has no encapsulating node. + fn query_encapsulating<'a, 'p, T>(&'a self, network_path: &'p [NodeId], caller: &str, query: impl FnOnce(NetworkView<'a, 'p>) -> Result) -> Option { + match self.view(network_path).and_then(query) { + Ok(value) => Some(value), + Err(NetworkError::NoEncapsulatingNode) => None, + Err(error) => { + log::error!("{error} in {caller}"); + None + } + } } /// Get the network which the encapsulating node of the currently viewed network is part of. Will always be None in the document network. pub fn encapsulating_network_metadata(&self, network_path: &[NodeId]) -> Option<&NodeNetworkMetadata> { - let mut encapsulating_path = network_path.to_vec(); - encapsulating_path.pop()?; - let Some(parent_metadata) = self.network_metadata(&encapsulating_path) else { - log::error!("Could not get parent network in encapsulating_node_metadata"); - return None; - }; - Some(parent_metadata) + self.query_encapsulating(network_path, "encapsulating_network_metadata", |view| view.encapsulating().map(|parent| parent.network_metadata())) } /// Get the node which encapsulates the currently viewed network. Will always be None in the document network. pub fn encapsulating_node(&self, network_path: &[NodeId]) -> Option<&DocumentNode> { - let mut encapsulating_path = network_path.to_vec(); - let encapsulating_node_id = encapsulating_path.pop()?; - let Some(encapsulating_node) = self.document_node(&encapsulating_node_id, &encapsulating_path) else { - log::error!("Could not get encapsulating node in encapsulating_node"); - return None; - }; - Some(encapsulating_node) + self.query_encapsulating(network_path, "encapsulating_node", |view| view.encapsulating_node()) } /// Get the node metadata for the node which encapsulates the currently viewed network. Will always be None in the document network. pub fn encapsulating_node_metadata(&self, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> { - let mut encapsulating_path = network_path.to_vec(); - let encapsulating_node_id = encapsulating_path.pop()?; - let Some(parent_metadata) = self.network_metadata(&encapsulating_path) else { - log::error!("Could not get parent network in encapsulating_node_metadata"); - return None; - }; - let Some(encapsulating_node_metadata) = parent_metadata.persistent_metadata.node_metadata.get(&encapsulating_node_id) else { - log::error!("Could not get encapsulating node metadata in encapsulating_node_metadata"); - return None; - }; - Some(encapsulating_node_metadata) + self.query_encapsulating(network_path, "encapsulating_node_metadata", |view| view.encapsulating_node_metadata()) } /// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself. @@ -150,128 +130,39 @@ impl NodeNetworkInterface { } pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 { - if self.number_of_displayed_inputs(node_id, network_path) > 1 { - let mut last_chain_node_distance = 0_u32; - // Iterate upstream from the layer, and get the number of nodes distance to the last node with Position::Chain - for (index, node_id) in self - .upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalPrimaryOutputFlow) - .skip(1) - .enumerate() - .collect::>() - { - // Check if the node is positioned as a chain - if self.is_chain(&node_id, network_path) { - last_chain_node_distance = (index as u32) + 1; - } else { - return last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1; - } - } - - last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1 - } else { - // Layer with no inputs has no chain - 0 - } + self.query(network_path, "chain_width", |view| Ok(view.chain_width(node_id))).unwrap_or_default() } /// Check if the specified node id is connected to the output pub fn connected_to_output(&self, target_node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(network) = self.nested_network(network_path) else { - log::error!("Could not get network in connected_to_output"); - return false; - }; - // If the node is the output then return true - if network - .exports - .iter() - .any(|export| if let NodeInput::Node { node_id, .. } = export { node_id == target_node_id } else { false }) - { - return true; - } - - // Get the outputs - let mut stack = network - .exports - .iter() - .filter_map(|output| if let NodeInput::Node { node_id, .. } = output { network.nodes.get(node_id) } else { None }) - .collect::>(); - let mut already_visited = HashSet::new(); - already_visited.extend( - network - .exports - .iter() - .filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id) } else { None }), - ); - - while let Some(node) = stack.pop() { - for input in &node.inputs { - if let &NodeInput::Node { node_id: ref_id, .. } = input { - // Skip if already viewed - if already_visited.contains(&ref_id) { - continue; - } - // If the target node is used as input then return true - if ref_id == *target_node_id { - return true; - } - // Add the referenced node to the stack - let Some(ref_node) = network.nodes.get(&ref_id) else { - continue; - }; - already_visited.insert(ref_id); - stack.push(ref_node); - } - } - } - - false + self.query(network_path, "connected_to_output", |view| Ok(view.connected_to_output(target_node_id))).unwrap_or_default() } pub fn number_of_imports(&self, network_path: &[NodeId]) -> usize { - // TODO: Use network.import_types.len() - if let Some(encapsulating_node) = self.encapsulating_node(network_path) { - encapsulating_node.inputs.len() - } else { - 0 - } + self.view(network_path).map(|view| view.number_of_imports()).unwrap_or_default() } pub fn number_of_exports(&self, network_path: &[NodeId]) -> usize { - if let Some(network) = self.nested_network(network_path) { network.exports.len() } else { 0 } + self.view(network_path).map(|view| view.number_of_exports()).unwrap_or_default() } pub(crate) fn number_of_displayed_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize { - let Some(node) = self.document_node(node_id, network_path) else { - log::error!("Could not get node {node_id} in number_of_displayed_inputs"); - return 0; - }; - node.inputs.iter().filter(|input| input.is_exposed()).count() + self.query(network_path, "number_of_displayed_inputs", |view| view.number_of_displayed_inputs(node_id)) + .unwrap_or_default() } pub fn number_of_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize { - let Some(node) = self.document_node(node_id, network_path) else { - log::error!("Could not get node {node_id} in number_of_inputs"); - return 0; - }; - node.inputs.len() + self.query(network_path, "number_of_inputs", |view| view.number_of_inputs(node_id)).unwrap_or_default() } /// Whether the node has an exposed input at index 0 to accept the horizontal flow from upstream. /// A node without one (e.g. a generator) can only be the most-upstream node in a chain. pub fn has_primary_input(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - self.input_from_connector(&InputConnector::node(*node_id, 0), network_path).is_some_and(|input| input.is_exposed()) + self.view(network_path).and_then(|view| view.has_primary_input(node_id)).unwrap_or_default() } pub fn number_of_outputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize { - let Some(implementation) = self.implementation(node_id, network_path) else { - log::error!("Could not get node {node_id} in number_of_outputs"); - return 0; - }; - match &implementation { - DocumentNodeImplementation::ProtoNode(_) => 1, - DocumentNodeImplementation::Network(nested_network) => nested_network.exports.len(), - DocumentNodeImplementation::Extract => 1, - } + self.query(network_path, "number_of_outputs", |view| view.number_of_outputs(node_id)).unwrap_or_default() } /// Creates a copy for each node by disconnecting nodes which are not connected to other copied nodes. @@ -350,19 +241,7 @@ impl NodeNetworkInterface { /// Create a node template from an existing node. pub fn create_node_template(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { - let Some(node) = self.document_node(node_id, network_path) else { - log::error!("Could not get node {node_id} in create_node_template"); - return None; - }; - let Some(node_metadata) = self.node_metadata(node_id, network_path).cloned() else { - log::error!("Could not get node_metadata in create_node_template"); - return None; - }; - - Some(NodeTemplate { - persistent_node_metadata: node_metadata.persistent_metadata, - document_node: node.clone(), - }) + self.query(network_path, "create_node_template", |view| view.create_node_template(node_id)) } /// Converts all node id inputs to a new id based on a HashMap. @@ -388,19 +267,14 @@ impl NodeNetworkInterface { } pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> { - let Some(network) = self.nested_network(network_path) else { - log::error!("Could not get network in input_from_connector"); - return None; - }; - match input_connector { - InputConnector::Node { node_id, input_index } => { - let Some(node) = network.nodes.get(node_id) else { - log::error!("Could not get node {node_id} in input_from_connector"); - return None; - }; - node.inputs.get(*input_index) + match self.view(network_path).and_then(|view| view.input(input_connector)) { + Ok(input) => Some(input), + // An out-of-range input index is an expected lookup miss, not an error worth logging + Err(NetworkError::InputNotFound { .. }) => None, + Err(error) => { + log::error!("{error} in input_from_connector"); + None } - InputConnector::Export(export_index) => network.exports.get(*export_index), } } @@ -806,86 +680,40 @@ impl NodeNetworkInterface { } pub fn previewing(&self, network_path: &[NodeId]) -> Previewing { - let Some(network_metadata) = self.network_metadata(network_path) else { - log::error!("Could not get nested network_metadata in previewing"); - return Previewing::No; - }; - network_metadata.persistent_metadata.previewing + self.query(network_path, "previewing", |view| Ok(view.previewing())).unwrap_or(Previewing::No) } /// Returns the root node (the node that the solid line is connect to), or None if no nodes are connected to the output pub fn root_node(&self, network_path: &[NodeId]) -> Option { - let Some(network) = self.nested_network(network_path) else { - log::error!("Could not get network in root_node"); - return None; - }; - let Some(network_metadata) = self.network_metadata(network_path) else { - log::error!("Could not get nested network_metadata in root_node"); - return None; - }; - match &network_metadata.persistent_metadata.previewing { - Previewing::Yes { root_node_to_restore } => *root_node_to_restore, - Previewing::No => network.exports.first().and_then(|export| { - if let NodeInput::Node { node_id, output_index, .. } = export { - Some(RootNode { - node_id: *node_id, - output_index: *output_index, - }) - } else { - None - } - }), - } + self.query(network_path, "root_node", |view| Ok(view.root_node())).flatten() } pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { - let Some(document_node) = self.document_node(node_id, network_path) else { - log::error!("Could not get document_node for node in reference: {node_id:?}"); - return None; - }; - match &document_node.implementation { - DocumentNodeImplementation::Network(_) => { - let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get reference for node in reference: {node_id:?}"); - return None; - }; - let Some(network_metadata) = node_metadata.persistent_metadata.network_metadata.as_ref() else { - log::error!("Network metadata must exist for network node in reference: {node_id:?}"); - return None; - }; - network_metadata.persistent_metadata.reference.clone().map(DefinitionIdentifier::Network) - } - DocumentNodeImplementation::ProtoNode(protonode_id) => Some(DefinitionIdentifier::ProtoNode(protonode_id.clone())), - _ => None, - } + self.query(network_path, "reference", |view| view.reference(node_id)).flatten() } pub fn implementation(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeImplementation> { - let Some(node) = self.document_node(node_id, network_path) else { - log::error!("Could not get implementation"); - return None; - }; - Some(&node.implementation) + self.query(network_path, "implementation", |view| view.implementation(node_id)) } pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str, network_path: &[NodeId]) -> Option<&Value> { - let metadata = self - .node_metadata(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; - metadata.persistent_metadata.input_data.get(key) + match self.view(network_path).and_then(|view| view.input_data(node_id, index, key)) { + Ok(value) => value, + // A missing input slot is the caller probing optional data, but any other failure is a corrupt network worth logging + Err(NetworkError::InputNotFound { .. }) => None, + Err(error) => { + log::error!("{error} in input_data"); + None + } + } } + pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputPersistentMetadata> { - let metadata = self - .node_metadata(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; - Some(&metadata.persistent_metadata) + 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> { - let metadata = self - .node_metadata(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; - Some(&metadata.transient_metadata) + 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]) { @@ -916,93 +744,35 @@ impl NodeNetworkInterface { /// Returns the display name of the node. If the display name is empty, it will return "Untitled Node" or "Untitled Layer" depending on the node type. pub fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { - let is_layer = self.is_layer(node_id, network_path); - - let display_name = if let Some(node_metadata) = self.node_metadata(node_id, network_path) { - node_metadata.persistent_metadata.display_name.clone() - } else { - log::error!("Could not get node_metadata in display_name"); - String::new() - }; - - if display_name.is_empty() { - if is_layer { - "Untitled Layer".to_string() - } else { - // TODO: Have this displayed in italics in the UI - self.implementation_name(node_id, network_path) - } - } else { - display_name - } + self.query(network_path, "display_name", |view| Ok(view.display_name(node_id))) + .unwrap_or_else(|| "Custom Node".to_string()) } /// The uneditable name in the Properties panel which represents the function name of the node implementation. pub fn implementation_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { - self.reference(node_id, network_path) - .map(|identifier| identifier.implementation_name_from_identifier()) - .unwrap_or("Custom Node".to_string()) + self.query(network_path, "implementation_name", |view| Ok(view.implementation_name(node_id))) + .unwrap_or_else(|| "Custom Node".to_string()) } pub fn is_locked(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get persistent node metadata in is_locked for node {node_id}"); - return false; - }; - node_metadata.persistent_metadata.locked + self.query(network_path, "is_locked", |view| view.is_locked(node_id)).unwrap_or_default() } pub fn is_pinned(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get persistent node metadata in is_pinned for node {node_id}"); - return false; - }; - node_metadata.persistent_metadata.pinned + self.query(network_path, "is_pinned", |view| view.is_pinned(node_id)).unwrap_or_default() } /// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last. pub fn ordered_pinned_nodes(&self, network_path: &[NodeId]) -> Vec { - let Some(network) = self.nested_network(network_path) else { return Vec::new() }; - - let order = self - .network_metadata(network_path) - .map(|network_metadata| network_metadata.persistent_metadata.pinned_node_order.clone()) - .unwrap_or_default(); - - // Follow the saved order, keeping only nodes that still exist and are still pinned - let mut pinned_nodes = order - .iter() - .copied() - .filter(|node_id| network.nodes.contains_key(node_id) && self.is_pinned(node_id, network_path)) - .collect::>(); - - // Append any pinned nodes missing from the saved order at the end - let mut unordered = network - .nodes - .keys() - .copied() - .filter(|node_id| self.is_pinned(node_id, network_path) && !order.contains(node_id)) - .collect::>(); - unordered.sort(); - pinned_nodes.extend(unordered); - - pinned_nodes + self.view(network_path).map(|view| view.ordered_pinned_nodes()).unwrap_or_default() } pub fn is_visible(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(node) = self.document_node(node_id, network_path) else { - log::error!("Could not get node in is_visible"); - return false; - }; - node.visible + self.query(network_path, "is_visible", |view| view.is_visible(node_id)).unwrap_or_default() } pub fn is_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get nested node_metadata in is_layer"); - return false; - }; - node_metadata.persistent_metadata.is_layer() + 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 { @@ -1022,73 +792,39 @@ impl NodeNetworkInterface { downstream_nodes.iter().any(|node_id| self.is_layer(node_id, network_path)) } - pub fn primary_input_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - self.input_from_connector(&InputConnector::node(*node_id, 0), network_path) - .and_then(|input| input.as_node()) - .is_some_and(|node_id| self.is_layer(&node_id, network_path)) + pub fn primary_input_connected_to_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { + self.query(network_path, "primary_input_connected_to_layer", |view| Ok(view.primary_input_connected_to_layer(node_id))) + .unwrap_or_default() } pub fn hidden_primary_export(&self, network_path: &[NodeId]) -> bool { - let Some((node_id, network_path)) = network_path.split_last() else { - // The document network does not have a hidden primary export - return false; - }; - self.hidden_primary_output(node_id, network_path) + self.view(network_path).map(|view| view.hidden_primary_export()).unwrap_or_default() } pub fn hidden_primary_output(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - match self.implementation(node_id, network_path) { - Some(DocumentNodeImplementation::Network(network)) => network.exports.first().is_none_or(|input| !input.is_exposed()), - _ => false, - } + self.view(network_path).and_then(|view| view.hidden_primary_output(node_id)).unwrap_or_default() } pub fn hidden_primary_import(&self, network_path: &[NodeId]) -> bool { - let Some((encapsulating_node_id, encapsulating_path)) = network_path.split_last() else { - return false; - }; - self.input_from_connector(&InputConnector::node(*encapsulating_node_id, 0), encapsulating_path) - .is_some_and(|input| !input.is_exposed()) + self.view(network_path).map(|view| view.hidden_primary_import()).unwrap_or_default() } pub fn is_absolute(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get node_metadata in is_absolute"); - return false; - }; - match &node_metadata.persistent_metadata.node_type_metadata { - NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Absolute(_)), - NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Absolute(_)), - } + self.query(network_path, "is_absolute", |view| view.is_absolute(node_id)).unwrap_or_default() } pub fn is_chain(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get node_metadata in is_chain"); - return false; - }; - match &node_metadata.persistent_metadata.node_type_metadata { - NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Chain), - _ => false, - } + self.query(network_path, "is_chain", |view| view.is_chain(node_id)).unwrap_or_default() } pub fn is_stack(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get node_metadata in is_stack"); - return false; - }; - match &node_metadata.persistent_metadata.node_type_metadata { - NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Stack(_)), - _ => false, - } + self.query(network_path, "is_stack", |view| view.is_stack(node_id)).unwrap_or_default() } /// Whether the node is an Artboard node by identity, regardless of whether it currently participates in the scene. /// Callers that care about scene membership should source their layers from the document structure or check connectivity separately. pub fn is_artboard(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - self.reference(node_id, network_path) - .is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into())) + self.view(network_path).map(|view| view.is_artboard(node_id)).unwrap_or_default() } /// All artboard layers that participate in the scene, excluding disconnected Artboard nodes. @@ -1290,34 +1026,18 @@ impl NodeNetworkInterface { } /// Gives an iterator to all nodes connected to the given nodes by all inputs (primary or primary + secondary depending on `only_follow_primary` choice), traversing backwards upstream starting from the given node's inputs. - pub fn upstream_flow_back_from_nodes<'a>(&'a self, mut node_ids: Vec, network_path: &'a [NodeId], mut flow_type: FlowType) -> impl Iterator + 'a { - let (Some(network), Some(network_metadata)) = (self.nested_network(network_path), self.network_metadata(network_path)) else { - log::error!("Could not get network or network_metadata in upstream_flow_back_from_nodes"); - return FlowIter { - stack: Vec::new(), - network: self.document_network(), - network_metadata: &self.network_metadata, - flow_type: FlowType::UpstreamFlow, - }; - }; - if matches!(flow_type, FlowType::LayerChildrenUpstreamFlow) { - node_ids = node_ids - .iter() - .filter_map(move |node_id| { - if self.is_layer(node_id, network_path) { - network.nodes.get(node_id).and_then(|node| node.inputs.get(1)).and_then(|input| input.as_node()) - } else { - Some(*node_id) - } - }) - .collect::>(); - flow_type = FlowType::UpstreamFlow; - }; - FlowIter { - stack: node_ids, - network, - network_metadata, - flow_type, + pub fn upstream_flow_back_from_nodes<'a>(&'a self, node_ids: Vec, network_path: &'a [NodeId], flow_type: FlowType) -> impl Iterator + 'a { + match self.view(network_path) { + Ok(view) => view.upstream_flow(node_ids, flow_type), + Err(error) => { + log::error!("{error} in upstream_flow_back_from_nodes"); + FlowIter { + stack: Vec::new(), + network: self.document_network(), + network_metadata: &self.network_metadata, + flow_type: FlowType::UpstreamFlow, + } + } } } 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 new file mode 100644 index 0000000000..f66c85d689 --- /dev/null +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs @@ -0,0 +1,426 @@ +use super::*; + +/// A failure to resolve part of the network or its metadata, returned by [`NetworkView`] accessors. +#[derive(Debug, Clone, PartialEq, thiserror::Error)] +pub enum NetworkError { + #[error("Network not found at path {path:?}")] + NetworkNotFound { path: Vec }, + #[error("Node {node_id} not found")] + NodeNotFound { node_id: NodeId }, + #[error("Node metadata for {node_id} not found")] + NodeMetadataNotFound { node_id: NodeId }, + #[error("Nested network metadata for node {node_id} not found")] + NestedMetadataNotFound { node_id: NodeId }, + #[error("Input {connector:?} not found")] + InputNotFound { connector: InputConnector }, + #[error("The document network has no encapsulating node")] + NoEncapsulatingNode, +} + +/// A read cursor over one network nesting level, resolving the network and its parallel metadata once instead of per query. +/// Obtained from [`NodeNetworkInterface::view`]; accessors return typed errors that callers at the message boundary may log. +#[derive(Clone, Copy)] +pub struct NetworkView<'a, 'p> { + pub(crate) interface: &'a NodeNetworkInterface, + pub(crate) path: &'p [NodeId], + pub(crate) network: &'a NodeNetwork, + pub(crate) metadata: &'a NodeNetworkMetadata, +} + +impl NodeNetworkInterface { + /// Resolves a read cursor for the network at `network_path`, walking the nested network and metadata trees once. + pub fn view<'a, 'p>(&'a self, network_path: &'p [NodeId]) -> Result, NetworkError> { + let network = self + .document_network() + .nested_network(network_path) + .ok_or_else(|| NetworkError::NetworkNotFound { path: network_path.to_vec() })?; + let metadata = self + .document_network_metadata() + .nested_metadata(network_path) + .ok_or_else(|| NetworkError::NetworkNotFound { path: network_path.to_vec() })?; + + Ok(NetworkView { + interface: self, + path: network_path, + network, + metadata, + }) + } +} + +impl<'a, 'p> NetworkView<'a, 'p> { + pub fn network(&self) -> &'a NodeNetwork { + self.network + } + + pub fn network_metadata(&self) -> &'a NodeNetworkMetadata { + self.metadata + } + + pub fn node(&self, node_id: &NodeId) -> Result<&'a DocumentNode, NetworkError> { + self.network.nodes.get(node_id).ok_or(NetworkError::NodeNotFound { node_id: *node_id }) + } + + pub fn node_metadata(&self, node_id: &NodeId) -> Result<&'a DocumentNodeMetadata, NetworkError> { + self.metadata + .persistent_metadata + .node_metadata + .get(node_id) + .ok_or(NetworkError::NodeMetadataNotFound { node_id: *node_id }) + } + + pub fn input(&self, input_connector: &InputConnector) -> Result<&'a NodeInput, NetworkError> { + let input = match input_connector { + InputConnector::Node { node_id, input_index } => self.node(node_id)?.inputs.get(*input_index), + InputConnector::Export(export_index) => self.network.exports.get(*export_index), + }; + input.ok_or(NetworkError::InputNotFound { connector: *input_connector }) + } + + /// The view over the network which contains this network's encapsulating node, or an error in the document network. + pub fn encapsulating(&self) -> Result, NetworkError> { + let (_, encapsulating_path) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?; + self.interface.view(encapsulating_path) + } + + /// The node which encapsulates this network, or an error in the document network. + pub fn encapsulating_node(&self) -> Result<&'a DocumentNode, NetworkError> { + let (node_id, _) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?; + self.encapsulating()?.node(node_id) + } + + /// The metadata of the node which encapsulates this network, or an error in the document network. + pub fn encapsulating_node_metadata(&self) -> Result<&'a DocumentNodeMetadata, NetworkError> { + let (node_id, _) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?; + self.encapsulating()?.node_metadata(node_id) + } + + pub fn implementation(&self, node_id: &NodeId) -> Result<&'a DocumentNodeImplementation, NetworkError> { + Ok(&self.node(node_id)?.implementation) + } + + pub fn reference(&self, node_id: &NodeId) -> Result, NetworkError> { + match self.implementation(node_id)? { + DocumentNodeImplementation::Network(_) => { + let node_metadata = self.node_metadata(node_id)?; + let network_metadata = node_metadata + .persistent_metadata + .network_metadata + .as_ref() + .ok_or(NetworkError::NestedMetadataNotFound { node_id: *node_id })?; + Ok(network_metadata.persistent_metadata.reference.clone().map(DefinitionIdentifier::Network)) + } + DocumentNodeImplementation::ProtoNode(protonode_id) => Ok(Some(DefinitionIdentifier::ProtoNode(protonode_id.clone()))), + _ => Ok(None), + } + } + + pub fn is_layer(&self, node_id: &NodeId) -> Result { + Ok(self.node_metadata(node_id)?.persistent_metadata.is_layer()) + } + + /// Whether the node is an Artboard node by identity, regardless of whether it currently participates in the scene. + pub fn is_artboard(&self, node_id: &NodeId) -> bool { + self.reference(node_id) + .ok() + .flatten() + .is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into())) + } + + /// The uneditable name in the Properties panel which represents the function name of the node implementation. + pub fn implementation_name(&self, node_id: &NodeId) -> String { + self.reference(node_id) + .ok() + .flatten() + .map(|identifier| identifier.implementation_name_from_identifier()) + .unwrap_or("Custom Node".to_string()) + } + + /// The display name of the node, falling back to "Untitled Layer" or the implementation name when unnamed. + pub fn display_name(&self, node_id: &NodeId) -> String { + let display_name = self.node_metadata(node_id).map(|metadata| metadata.persistent_metadata.display_name.clone()).unwrap_or_default(); + + if display_name.is_empty() { + if self.is_layer(node_id).unwrap_or_default() { + "Untitled Layer".to_string() + } else { + // TODO: Have this displayed in italics in the UI + self.implementation_name(node_id) + } + } else { + display_name + } + } + + /// The current selection of this network, filtered to nodes that still exist. + pub fn selected_nodes(&self) -> SelectedNodes { + self.metadata + .persistent_metadata + .selection_undo_history + .back() + .cloned() + .unwrap_or_default() + .filtered_selected_nodes(|node_id| self.metadata.persistent_metadata.node_metadata.contains_key(node_id)) + } + + pub fn primary_input_connected_to_layer(&self, node_id: &NodeId) -> bool { + self.input(&InputConnector::node(*node_id, 0)) + .ok() + .and_then(|input| input.as_node()) + .is_some_and(|upstream_id| self.is_layer(&upstream_id).unwrap_or_default()) + } + + /// Whether the node's signature fits the layer form: one output, at most two exposed leading inputs, and no exposed parameters. + pub fn is_eligible_to_be_layer(&self, node_id: &NodeId) -> Result { + let node = self.node(node_id)?; + let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count(); + let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed()); + let output_count = self.number_of_outputs(node_id)?; + + Ok(!self.hidden_primary_output(node_id)? && output_count == 1 && (input_count <= 2) && parameters_hidden) + } + + pub fn is_locked(&self, node_id: &NodeId) -> Result { + Ok(self.node_metadata(node_id)?.persistent_metadata.locked) + } + + pub fn is_pinned(&self, node_id: &NodeId) -> Result { + Ok(self.node_metadata(node_id)?.persistent_metadata.pinned) + } + + pub fn is_visible(&self, node_id: &NodeId) -> Result { + Ok(self.node(node_id)?.visible) + } + + pub fn is_absolute(&self, node_id: &NodeId) -> Result { + Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata { + NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Absolute(_)), + NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Absolute(_)), + }) + } + + pub fn is_chain(&self, node_id: &NodeId) -> Result { + Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata { + NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Chain), + _ => false, + }) + } + + pub fn is_stack(&self, node_id: &NodeId) -> Result { + Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata { + NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Stack(_)), + _ => false, + }) + } + + pub fn number_of_inputs(&self, node_id: &NodeId) -> Result { + Ok(self.node(node_id)?.inputs.len()) + } + + pub fn number_of_displayed_inputs(&self, node_id: &NodeId) -> Result { + Ok(self.node(node_id)?.inputs.iter().filter(|input| input.is_exposed()).count()) + } + + pub fn number_of_outputs(&self, node_id: &NodeId) -> Result { + Ok(match self.implementation(node_id)? { + DocumentNodeImplementation::Network(nested_network) => nested_network.exports.len(), + _ => 1, + }) + } + + /// The number of imports as defined by the encapsulating node's input count, or zero for the document network. + pub fn number_of_imports(&self) -> usize { + self.encapsulating_node().map_or(0, |node| node.inputs.len()) + } + + pub fn number_of_exports(&self) -> usize { + self.network.exports.len() + } + + pub fn has_primary_input(&self, node_id: &NodeId) -> Result { + Ok(self.input(&InputConnector::node(*node_id, 0)).is_ok_and(|input| input.is_exposed())) + } + + pub fn hidden_primary_output(&self, node_id: &NodeId) -> Result { + Ok(match self.implementation(node_id)? { + DocumentNodeImplementation::Network(network) => network.exports.first().is_none_or(|input| !input.is_exposed()), + _ => false, + }) + } + + pub fn hidden_primary_export(&self) -> bool { + let Some((node_id, _)) = self.path.split_last() else { return false }; + self.encapsulating().and_then(|parent| parent.hidden_primary_output(node_id)).unwrap_or(false) + } + + pub fn hidden_primary_import(&self) -> bool { + self.encapsulating_node().is_ok_and(|node| node.inputs.first().is_some_and(|input| !input.is_exposed())) + } + + pub fn previewing(&self) -> Previewing { + self.metadata.persistent_metadata.previewing + } + + /// The root node (the node that the solid line is connected to), or None if no nodes are connected to the output. + pub fn root_node(&self) -> Option { + match &self.metadata.persistent_metadata.previewing { + Previewing::Yes { root_node_to_restore } => *root_node_to_restore, + Previewing::No => self.network.exports.first().and_then(|export| { + if let NodeInput::Node { node_id, output_index, .. } = export { + Some(RootNode { + node_id: *node_id, + output_index: *output_index, + }) + } else { + None + } + }), + } + } + + pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str) -> Result, NetworkError> { + Ok(self.persistent_input_metadata(node_id, index)?.input_data.get(key)) + } + + pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize) -> Result<&'a InputPersistentMetadata, 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.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)), + NodeInput::Import { import_index, .. } => Some(OutputConnector::Import(*import_index)), + _ => None, + }) + } + + /// Whether the node reaches the exports by following wires downstream. + // O(nodes + wires) reachability walk + pub fn connected_to_output(&self, target_node_id: &NodeId) -> bool { + if self + .network + .exports + .iter() + .any(|export| if let NodeInput::Node { node_id, .. } = export { node_id == target_node_id } else { false }) + { + return true; + } + + let mut stack = self + .network + .exports + .iter() + .filter_map(|output| if let NodeInput::Node { node_id, .. } = output { self.network.nodes.get(node_id) } else { None }) + .collect::>(); + let mut already_visited = HashSet::new(); + already_visited.extend( + self.network + .exports + .iter() + .filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id) } else { None }), + ); + + while let Some(node) = stack.pop() { + for input in &node.inputs { + if let &NodeInput::Node { node_id: ref_id, .. } = input { + if already_visited.contains(&ref_id) { + continue; + } + if ref_id == *target_node_id { + return true; + } + let Some(ref_node) = self.network.nodes.get(&ref_id) else { continue }; + already_visited.insert(ref_id); + stack.push(ref_node); + } + } + } + + false + } + + /// An iterator of all nodes connected to the given nodes by the flow type, traversing backwards upstream from the given nodes' inputs. + pub(crate) fn upstream_flow(&self, mut node_ids: Vec, mut flow_type: FlowType) -> FlowIter<'a> { + if matches!(flow_type, FlowType::LayerChildrenUpstreamFlow) { + node_ids = node_ids + .iter() + .filter_map(|node_id| { + if self.is_layer(node_id).unwrap_or_default() { + self.network.nodes.get(node_id).and_then(|node| node.inputs.get(1)).and_then(|input| input.as_node()) + } else { + Some(*node_id) + } + }) + .collect::>(); + flow_type = FlowType::UpstreamFlow; + }; + FlowIter { + stack: node_ids, + network: self.network, + network_metadata: self.metadata, + flow_type, + } + } + + /// The distance in grid cells from the layer to the end of its chain, or zero for a layer with no chain. + pub fn chain_width(&self, node_id: &NodeId) -> u32 { + if self.number_of_displayed_inputs(node_id).unwrap_or_default() > 1 { + let mut last_chain_node_distance = 0_u32; + for (index, node_id) in self.upstream_flow(vec![*node_id], FlowType::HorizontalPrimaryOutputFlow).skip(1).enumerate() { + if self.is_chain(&node_id).unwrap_or_default() { + last_chain_node_distance = (index as u32) + 1; + } else { + return last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1; + } + } + + last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1 + } else { + // A layer with no secondary input has no chain + 0 + } + } + + /// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last. + pub fn ordered_pinned_nodes(&self) -> Vec { + let order = &self.metadata.persistent_metadata.pinned_node_order; + let is_pinned = |node_id: &NodeId| self.is_pinned(node_id).unwrap_or_default(); + + // Follow the saved order, keeping only nodes that still exist and are still pinned + let mut pinned_nodes = order + .iter() + .copied() + .filter(|node_id| self.network.nodes.contains_key(node_id) && is_pinned(node_id)) + .collect::>(); + + // Append any pinned nodes missing from the saved order at the end + let mut unordered = self.network.nodes.keys().copied().filter(|node_id| is_pinned(node_id) && !order.contains(node_id)).collect::>(); + unordered.sort(); + pinned_nodes.extend(unordered); + + pinned_nodes + } + + /// Create a node template from an existing node. + pub fn create_node_template(&self, node_id: &NodeId) -> Result { + let node = self.node(node_id)?; + let node_metadata = self.node_metadata(node_id)?; + + Ok(NodeTemplate { + persistent_node_metadata: node_metadata.persistent_metadata.clone(), + document_node: node.clone(), + }) + } +}