diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index aec19541a2..8d692d771b 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -17,7 +17,7 @@ use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ}; -use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate, OutputConnector}; +use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate}; use crate::messages::portfolio::document::utility_types::nodes::RawBuffer; use crate::messages::portfolio::utility_types::PanelType; use crate::messages::portfolio::utility_types::PersistentData; @@ -2787,14 +2787,9 @@ impl DocumentMessageHandler { .popover_layout({ // Showing only compatible types let compatible_type = selected_layer.and_then(|layer| { - let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &self.network_interface); - let node_type = graph_layer.horizontal_layer_flow().nth(1); - if let Some(node_id) = node_type { - let (output_type, _) = self.network_interface.output_type(&OutputConnector::node(node_id, 0), &self.selection_network_path); - Some(format!("type:{}", output_type.nested_type())) - } else { - None - } + self.network_interface + .upstream_output_connector(&InputConnector::node(layer.to_node(), 1), &[]) + .and_then(|upstream_output| self.network_interface.output_type(&upstream_output, &[]).add_node_string()) }); let mut node_chooser = NodeCatalog::new(); diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 4079fefd32..b25e7905a5 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -303,8 +303,8 @@ impl<'a> ModifyInputsContext<'a> { // If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`. // TODO: Allow the 'Path' node to operate on table data by utilizing the reference (index or ID?) for each row. if node_definition.identifier == "Path" { - let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone(); - if layer_input_type == concrete!(Table) { + let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).into_compiled_nested_type(); + if layer_input_type == Some(concrete!(Table)) { let Some(flatten_path_definition) = resolve_document_node_type("Flatten Path") else { log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id"); return None; diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 6337a2566d..688f455f5b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1,4 +1,4 @@ -use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode}; +use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart}; use super::{document_node_definitions, node_properties}; use crate::consts::GRID_SIZE; use crate::messages::input_mapper::utility_types::macros::action_keys; @@ -10,13 +10,13 @@ use crate::messages::portfolio::document::node_graph::utility_types::{ContextMen use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; use crate::messages::portfolio::document::utility_types::network_interface::{ - self, FlowType, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, + self, FlowType, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, }; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode}; +use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed; use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; @@ -878,7 +878,7 @@ impl<'a> MessageHandler> for NodeG }; let Some(output_connector) = output_connector else { return }; self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path); - self.wire_in_progress_type = FrontendGraphDataType::from_type(&network_interface.input_type(clicked_input, breadcrumb_network_path).0); + self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&network_interface.input_type(clicked_input, breadcrumb_network_path)); return; } @@ -888,8 +888,8 @@ impl<'a> MessageHandler> for NodeG self.initial_disconnecting = false; self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path); - let (output_type, source) = &network_interface.output_type(&clicked_output, breadcrumb_network_path); - self.wire_in_progress_type = FrontendGraphDataType::displayed_type(output_type, source); + let output_type = network_interface.output_type(&clicked_output, breadcrumb_network_path); + self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type); self.update_node_graph_hints(responses); return; @@ -1206,18 +1206,14 @@ impl<'a> MessageHandler> for NodeG return; } + let compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string(); + // Get the output types from the network interface - let (output_type, type_source) = network_interface.output_type(&output_connector, selection_network_path); let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { warn!("No network_metadata"); return; }; - let compatible_type = match type_source { - TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None, - _ => Some(format!("type:{}", output_type.nested_type())), - }; - let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. }; let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. }; let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x; @@ -1638,7 +1634,7 @@ impl<'a> MessageHandler> for NodeG responses.add(DocumentMessage::DocumentStructureChanged); responses.add(PropertiesPanelMessage::Refresh); if breadcrumb_network_path == selection_network_path && graph_view_overlay_open { - let nodes = self.collect_nodes(network_interface, breadcrumb_network_path); + let nodes = network_interface.collect_nodes(&self.node_graph_errors, breadcrumb_network_path); self.frontend_nodes = nodes.iter().map(|node| node.id).collect(); responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes }); responses.add(NodeGraphMessage::UpdateVisibleNodes); @@ -1652,6 +1648,7 @@ impl<'a> MessageHandler> for NodeG has_left_input_wire, }); responses.add(NodeGraphMessage::SendSelectedNodes); + responses.add(NodeGraphMessage::SendWires); self.update_node_graph_hints(responses); } } @@ -1990,12 +1987,7 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SendGraph); } NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => { - for (path, node_type) in resolved_types.add { - network_interface.resolved_types.types.insert(path.to_vec(), node_type); - } - for path in resolved_types.remove { - network_interface.resolved_types.types.remove(&path.to_vec()); - } + network_interface.resolved_types.update(resolved_types); self.node_graph_errors = node_graph_errors; } NodeGraphMessage::UpdateActionButtons => { @@ -2109,16 +2101,7 @@ impl NodeGraphMessageHandler { .popover_layout({ // Showing only compatible types let compatible_type = match (selection_includes_layers, has_multiple_selection, selected_layer) { - (true, false, Some(layer)) => { - let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface); - let node_type = graph_layer.horizontal_layer_flow().nth(1); - if let Some(node_id) = node_type { - let (output_type, _) = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]); - Some(format!("type:{}", output_type.nested_type())) - } else { - None - } - } + (true, false, Some(layer)) => network_interface.output_type(&OutputConnector::node(layer.to_node(), 1), &[]).add_node_string(), _ => None, }; @@ -2431,17 +2414,10 @@ impl NodeGraphMessageHandler { .icon(Some("Node".to_string())) .tooltip("Add an operation to the end of this layer's chain of nodes") .popover_layout({ - let layer_identifier = LayerNodeIdentifier::new(layer, context.network_interface); - let compatible_type = { - let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, context.network_interface); - let node_type = graph_layer.horizontal_layer_flow().nth(1); - if let Some(node_id) = node_type { - let (output_type, _) = context.network_interface.output_type(&OutputConnector::node(node_id, 0), &[]); - Some(format!("type:{}", output_type.nested_type())) - } else { - None - } - }; + let compatible_type = context + .network_interface + .upstream_output_connector(&InputConnector::node(layer, 1), &[]) + .and_then(|upstream_output| context.network_interface.output_type(&upstream_output, &[]).add_node_string()); let mut node_chooser = NodeCatalog::new(); node_chooser.intial_search = compatible_type.unwrap_or("".to_string()); @@ -2523,94 +2499,6 @@ impl NodeGraphMessageHandler { added_wires } - fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec { - let Some(network) = network_interface.nested_network(breadcrumb_network_path) else { - log::error!("Could not get nested network when collecting nodes"); - return Vec::new(); - }; - let mut nodes = Vec::new(); - for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::>() { - let node_id_path = [breadcrumb_network_path, &[node_id]].concat(); - - let primary_input_connector = InputConnector::node(node_id, 0); - - let primary_input = if network_interface - .input_from_connector(&primary_input_connector, breadcrumb_network_path) - .is_some_and(|input| input.is_exposed()) - { - network_interface.frontend_input_from_connector(&primary_input_connector, breadcrumb_network_path) - } else { - None - }; - let exposed_inputs = (1..network_interface.number_of_inputs(&node_id, breadcrumb_network_path)) - .filter_map(|input_index| network_interface.frontend_input_from_connector(&InputConnector::node(node_id, input_index), breadcrumb_network_path)) - .collect(); - - let primary_output = network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, 0), breadcrumb_network_path); - - let exposed_outputs = (1..network_interface.number_of_outputs(&node_id, breadcrumb_network_path)) - .filter_map(|output_index| network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, output_index), breadcrumb_network_path)) - .collect(); - let (primary_output_connected_to_layer, primary_input_connected_to_layer) = if network_interface.is_layer(&node_id, breadcrumb_network_path) { - ( - network_interface.primary_output_connected_to_layer(&node_id, breadcrumb_network_path), - network_interface.primary_input_connected_to_layer(&node_id, breadcrumb_network_path), - ) - } else { - (false, false) - }; - - let is_export = network_interface - .input_from_connector(&InputConnector::Export(0), breadcrumb_network_path) - .is_some_and(|export| export.as_node().is_some_and(|export_node_id| node_id == export_node_id)); - let is_root_node = network_interface.root_node(breadcrumb_network_path).is_some_and(|root_node| root_node.node_id == node_id); - - let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) else { - log::error!("Could not get position for node: {node_id}"); - continue; - }; - let previewed = is_export && !is_root_node; - - let locked = network_interface.is_locked(&node_id, breadcrumb_network_path); - - let errors = self - .node_graph_errors - .iter() - .find(|error| error.node_path == node_id_path) - .map(|error| format!("{:?}", error.error.clone())) - .or_else(|| { - if self.node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) { - Some("Node graph type error within this node".to_string()) - } else { - None - } - }); - - nodes.push(FrontendNode { - id: node_id, - is_layer: network_interface - .node_metadata(&node_id, breadcrumb_network_path) - .is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()), - can_be_layer: network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path), - reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(), - display_name: network_interface.display_name(&node_id, breadcrumb_network_path), - primary_input, - exposed_inputs, - primary_output, - exposed_outputs, - primary_output_connected_to_layer, - primary_input_connected_to_layer, - position, - previewed, - visible, - locked, - errors, - }); - } - - nodes - } - fn collect_subgraph_names(network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Option> { let mut current_network_path = vec![]; let mut current_network = network_interface.nested_network(¤t_network_path).unwrap(); diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 24328f3539..971c6cfa2c 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -8,9 +8,9 @@ use crate::messages::prelude::*; use choice::enum_choice; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; -use graph_craft::Type; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; +use graph_craft::{Type, concrete}; use graphene_std::NodeInputDecleration; use graphene_std::animation::RealTimeMode; use graphene_std::extract_xy::XY; @@ -1550,7 +1550,11 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper input_type.clone() } - _ => context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path).0, + _ => context + .network_interface + .input_type(&InputConnector::node(node_id, input_index), context.selection_network_path) + .into_compiled_nested_type() + .unwrap_or(concrete!(())), }; property_from_type(node_id, input_index, &input_type, number_options, unit_suffix, display_decimal_places, step, context).unwrap_or_else(|value| value) @@ -1932,7 +1936,7 @@ pub struct ParameterWidgetsInfo<'a> { impl<'a> ParameterWidgetsInfo<'a> { pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> { let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path); - let input_type = FrontendGraphDataType::from_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path).0); + let input_type = FrontendGraphDataType::displayed_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path)); let document_node = context.network_interface.document_node(&node_id, context.selection_network_path); ParameterWidgetsInfo { diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index b3ed877769..07741ee003 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -1,10 +1,11 @@ -use crate::messages::portfolio::document::utility_types::network_interface::TypeSource; use glam::IVec2; use graph_craft::document::NodeId; use graph_craft::document::value::TaggedValue; use graphene_std::Type; use std::borrow::Cow; +use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::TypeSource; + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum FrontendGraphDataType { #[default] @@ -42,10 +43,10 @@ impl FrontendGraphDataType { } } - pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self { - match type_source { - TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General, - _ => Self::from_type(input), + pub fn displayed_type(type_source: &TypeSource) -> Self { + match type_source.compiled_nested_type() { + Some(nested_type) => Self::from_type(&nested_type), + None => Self::General, } } } @@ -58,8 +59,6 @@ pub struct FrontendGraphInput { pub description: String, #[serde(rename = "resolvedType")] pub resolved_type: String, - #[serde(rename = "validTypes")] - pub valid_types: Vec, #[serde(rename = "connectedTo")] /// Either "nothing", "import index {index}", or "{node name} output {output_index}". pub connected_to: String, 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 16e4ed2af7..96095de94a 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1,35 +1,34 @@ -mod deserialization; - use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier, NodeRelations}; use super::misc::PTZ; use super::nodes::SelectedNodes; use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP}; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_definitions::{DocumentNodeDefinition, resolve_document_node_type}; -use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; +use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType}; +use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::ResolvedDocumentNodeTypes; use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode; use deserialization::deserialize_node_persistent_metadata; use glam::{DAffine2, DVec2, IVec2}; +use graph_craft::Type; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; -use graph_craft::{Type, concrete}; -use graphene_std::Artboard; use graphene_std::math::quad::Quad; use graphene_std::subpath::Subpath; -use graphene_std::table::Table; use graphene_std::transform::Footprint; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::{PointId, Vector, VectorModificationType}; -use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes; -use interpreted_executor::node_registry::NODE_REGISTRY; use kurbo::BezPath; use serde_json::{Value, json}; use std::collections::{HashMap, HashSet, VecDeque}; -use std::hash::{DefaultHasher, Hash, Hasher}; +use std::hash::Hash; use std::ops::Deref; +mod deserialization; +pub mod node_graph; +pub mod resolved_types; + /// All network modifications should be done through this API, so the fields cannot be public. However, all fields within this struct can be public since it it not possible to have a public mutable reference. #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] pub struct NodeNetworkInterface { @@ -228,31 +227,6 @@ impl NodeNetworkInterface { layers } - 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 = 0u32; - // 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 * 7 + 1; - } - } - - last_chain_node_distance * 7 + 1 - } else { - // Layer with no inputs has no chain - 0 - } - } - /// 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 { @@ -453,12 +427,12 @@ impl NodeNetworkInterface { *input = NodeInput::Node { node_id: new_id, output_index }; } else { // Disconnect node input if it is not connected to another node in new_ids - let tagged_value = TaggedValue::from_type_or_none(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0); + let tagged_value = self.tagged_value_from_input(&InputConnector::node(*node_id, input_index), network_path); *input = NodeInput::value(tagged_value, true); } } else if let &mut NodeInput::Network { .. } = input { // Always disconnect network node input - let tagged_value = TaggedValue::from_type_or_none(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0); + let tagged_value = self.tagged_value_from_input(&InputConnector::node(*node_id, input_index), network_path); *input = NodeInput::value(tagged_value, true); } } @@ -466,8 +440,8 @@ impl NodeNetworkInterface { } /// Try and get the [`DocumentNodeDefinition`] for a node - pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> { - let metadata = self.node_metadata(&node_id, network_path)?; + pub fn get_node_definition(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeDefinition> { + let metadata = self.node_metadata(node_id, network_path)?; resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) } @@ -488,278 +462,6 @@ impl NodeNetworkInterface { } } - /// Try and get the [`Type`] for any [`InputConnector`] based on the `self.resolved_types`. - fn node_type_from_compiled(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<(Type, TypeSource)> { - let (node_id, input_index) = match *input_connector { - InputConnector::Node { node_id, input_index } => (node_id, input_index), - InputConnector::Export(export_index) => { - let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else { - // The outermost network export defaults to a Table. - return Some((concrete!(Table), TypeSource::OuterMostExportDefault)); - }; - - let output_type = self.output_type(&OutputConnector::node(*encapsulating_node_id, export_index), encapsulating_node_id_path); - return Some(output_type); - } - }; - let Some(node) = self.document_node(&node_id, network_path) else { - log::error!("Could not get node {node_id} in input_type"); - return None; - }; - // If the input_connector is a NodeInput::Value, return the type of the tagged value. - if let Some(value) = node.inputs.get(input_index).and_then(|input| input.as_value()) { - return Some((value.ty(), TypeSource::TaggedValue)); - } - let node_id_path = [network_path, &[node_id]].concat(); - match &node.implementation { - DocumentNodeImplementation::Network(_nested_network) => { - // Attempt to resolve where this import is within the nested network (it may be connected to the node or directly to an export) - let outwards_wires = self.outward_wires(&node_id_path); - let inputs_using_import = outwards_wires.and_then(|outwards_wires| outwards_wires.get(&OutputConnector::Import(input_index))); - let first_input = inputs_using_import.and_then(|input| input.first()).copied(); - - if inputs_using_import.is_some_and(|inputs| inputs.len() > 1) { - warn!("Found multiple inputs using an import. Using the type of the first one."); - } - - if let Some(input_connector) = first_input { - self.node_type_from_compiled(&input_connector, &node_id_path) - } - // Nothing is connected to the import - else { - None - } - } - DocumentNodeImplementation::ProtoNode(_) => { - // Offset the input index by 1 since the proto node also includes the type of the input passed as a call argument. - self.resolved_types - .types - .get(node_id_path.as_slice()) - .and_then(|node_types| node_types.inputs.get(input_index + 1).cloned()) - .map(|node_types| (node_types, TypeSource::Compiled)) - } - DocumentNodeImplementation::Extract => None, - } - } - - /// Guess the type from the node based on a document node default or a random protonode definition. - fn guess_type_from_node(&mut self, network_path: &mut Vec, node_id: NodeId, input_index: usize) -> (Type, TypeSource) { - // Try and get the default value from the document node definition - if let Some(value) = self - .get_node_definition(network_path, node_id) - .and_then(|definition| definition.node_template.document_node.inputs.get(input_index)) - .and_then(|input| input.as_value()) - { - return (value.ty(), TypeSource::DocumentNodeDefault); - } - - let Some(node) = self.document_node(&node_id, network_path) else { - return (concrete!(()), TypeSource::Error("node id {node_id:?} not in network {network_path:?}")); - }; - - let node_id_path = [network_path.as_slice(), &[node_id]].concat(); - match &node.implementation { - DocumentNodeImplementation::ProtoNode(protonode) => { - let Some(node_types) = random_protonode_implementation(protonode) else { - return (concrete!(()), TypeSource::Error("could not resolve protonode")); - }; - - let skip_footprint = 1; - - let Some(input_type) = std::iter::once(node_types.call_argument.clone()).chain(node_types.inputs.clone()).nth(input_index + skip_footprint) else { - log::error!("Could not get type for {node_id_path:?}, input: {input_index}"); - return (concrete!(()), TypeSource::Error("could not get the protonode's input")); - }; - - (input_type, TypeSource::RandomProtonodeImplementation) - } - DocumentNodeImplementation::Network(_network) => { - // Attempt to resolve where this import is within the nested network - let outwards_wires = self.outward_wires(&node_id_path); - let inputs_using_import = outwards_wires.and_then(|outwards_wires| outwards_wires.get(&OutputConnector::Import(input_index))); - let first_input = inputs_using_import.and_then(|input| input.first()).copied(); - - if let Some(InputConnector::Node { - node_id: child_id, - input_index: child_input_index, - }) = first_input - { - network_path.push(node_id); - let result = self.guess_type_from_node(network_path, child_id, child_input_index); - network_path.pop(); - return result; - } - - // Input is disconnected - (concrete!(()), TypeSource::Error("disconnected network input")) - } - _ => (concrete!(()), TypeSource::Error("implementation is not network or protonode")), - } - } - - /// Get the [`Type`] for any InputConnector - pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { - if let Some(result) = self.node_type_from_compiled(input_connector, network_path) { - return result; - } - - // Resolve types from proto nodes in node_registry - let Some(node_id) = input_connector.node_id() else { - return (concrete!(()), TypeSource::Error("input connector is not a node")); - }; - - self.guess_type_from_node(&mut network_path.to_vec(), node_id, input_connector.input_index()) - } - - pub fn valid_input_types(&mut 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)]; - }; - let Some(implementation) = self.implementation(node_id, network_path) else { - log::error!("Could not get node implementation in valid_input_types"); - return Vec::new(); - }; - 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 valid_input_types"); - return Vec::new(); - }; - let Some(inputs_from_import) = outward_wires.get(&OutputConnector::Import(*input_index)) else { - log::error!("Could not get inputs from import in valid_input_types"); - return Vec::new(); - }; - - let intersection: HashSet = inputs_from_import - .clone() - .iter() - .map(|input_connector| self.valid_input_types(input_connector, &nested_path)) - .map(|vec| vec.into_iter().collect::>()) - .fold(None, |acc: Option>, set| match acc { - Some(acc_set) => Some(acc_set.intersection(&set).cloned().collect()), - None => Some(set), - }) - .unwrap_or_default(); - - intersection.into_iter().collect::>() - } - DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { - let Some(implementations) = NODE_REGISTRY.get(proto_node_identifier) else { - log::error!("Protonode {proto_node_identifier:?} not found in registry"); - return Vec::new(); - }; - let number_of_inputs = self.number_of_inputs(node_id, network_path); - implementations - .iter() - .filter_map(|(node_io, _)| { - let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| { - let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0; - // Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type. - // For example a node input of (Footprint) -> Vector would not be compatible with () -> Vector - node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type) - }); - if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } - }) - .collect::>() - } - DocumentNodeImplementation::Extract => { - log::error!("Input types for extract node not supported"); - Vec::new() - } - } - } - - /// Retrieves the output types for a given document node and its exports. - /// - /// This function traverses the node and its nested network structure (if applicable) to determine - /// the types of all outputs, including the primary output and any additional exports. - /// - /// # Arguments - /// - /// * `node` - A reference to the `DocumentNode` for which to determine output types. - /// * `resolved_types` - A reference to `ResolvedDocumentNodeTypes` containing pre-resolved type information. - /// * `node_id_path` - A slice of `NodeId`s representing the path to the current node in the document graph. - /// - /// # Returns - /// - /// A `Vec>` where: - /// - The first element is the primary output type of the node. - /// - Subsequent elements are types of additional exports (if the node is a network). - /// - `None` values indicate that a type couldn't be resolved for a particular output. - /// - /// # Behavior - /// - /// 1. Retrieves the primary output type from `resolved_types`. - /// 2. If the node is a network: - /// - Iterates through its exports (skipping the first/primary export). - /// - For each export, traverses the network until reaching a protonode or terminal condition. - /// - Determines the output type based on the final node/value encountered. - /// 3. Collects and returns all resolved types. - /// - /// # Note - /// - /// This function assumes that export indices and node IDs always exist within their respective - /// collections. It will panic if these assumptions are violated. - /// - pub fn output_type(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { - match output_connector { - OutputConnector::Node { node_id, output_index } => { - let Some(implementation) = self.implementation(node_id, network_path) else { - log::error!("Could not get output type for node {node_id} output index {output_index}. This node is no longer supported, and needs to be upgraded."); - return (concrete!(()), TypeSource::Error("Could not get implementation")); - }; - - // If the node is not a protonode, get types by traversing across exports until a proto node is reached. - match &implementation { - graph_craft::document::DocumentNodeImplementation::Network(internal_network) => { - let Some(export) = internal_network.exports.get(*output_index) else { - return (concrete!(()), TypeSource::Error("Could not get export index")); - }; - match export { - NodeInput::Node { - node_id: nested_node_id, - output_index, - .. - } => self.output_type(&OutputConnector::node(*nested_node_id, *output_index), &[network_path, &[*node_id]].concat()), - NodeInput::Value { tagged_value, .. } => (tagged_value.ty(), TypeSource::TaggedValue), - NodeInput::Network { .. } => { - // let mut encapsulating_path = network_path.to_vec(); - // let encapsulating_node = encapsulating_path.pop().expect("No imports exist in document network"); - // self.input_type(&InputConnector::node(encapsulating_node, *import_index), network_path) - (concrete!(()), TypeSource::Error("Could not type from network")) - } - NodeInput::Scope(_) => todo!(), - NodeInput::Inline(_) => todo!(), - NodeInput::Reflection(_) => todo!(), - } - } - graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode) => { - let node_id_path = &[network_path, &[*node_id]].concat(); - self.resolved_types - .types - .get(node_id_path) - .map(|ty| (ty.output.clone(), TypeSource::Compiled)) - .or_else(|| { - let node_types = random_protonode_implementation(protonode)?; - Some((node_types.return_value.clone(), TypeSource::RandomProtonodeImplementation)) - }) - .unwrap_or((concrete!(()), TypeSource::Error("Could not get protonode implementation"))) - } - graph_craft::document::DocumentNodeImplementation::Extract => (concrete!(()), TypeSource::Error("extract node")), - } - } - OutputConnector::Import(import_index) => { - let Some((encapsulating_node, encapsulating_path)) = network_path.split_last() else { - log::error!("Cannot get type of import in document network"); - return (concrete!(()), TypeSource::Error("Cannot get import type in document network")); - }; - self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path) - } - } - } - pub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { let top_left_position = self .node_click_targets(node_id, network_path) @@ -779,249 +481,6 @@ impl NodeNetworkInterface { }) } - pub fn frontend_imports(&mut 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 { - log::error!("Could not get node {node_id} in network {encapsulating_network_path:?}"); - return Vec::new(); - }; - let mut frontend_imports = (0..node.inputs.len()) - .map(|import_index| self.frontend_output_from_connector(&OutputConnector::Import(import_index), network_path)) - .collect::>(); - if frontend_imports.is_empty() { - frontend_imports.push(None); - } - frontend_imports - } - // In the document network display no imports - None => Vec::new(), - } - } - - pub fn frontend_exports(&mut 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() { - frontend_exports.push(None); - } - 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 { - log::error!("Could not get all nodes bounding box in load_export_ports"); - return None; - }; - let Some(network) = self.nested_network(network_path) else { - log::error!("Could not get current network in load_export_ports"); - return None; - }; - - let Some(network_metadata) = self.network_metadata(network_path) else { - log::error!("Could not get nested network_metadata in load_export_ports"); - return None; - }; - let node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport; - let target_viewport_top_left = DVec2::new(IMPORTS_TO_LEFT_EDGE_PIXEL_GAP as f64, IMPORTS_TO_TOP_EDGE_PIXEL_GAP as f64); - - let node_graph_pixel_offset_top_left = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_left); - - // A 5x5 grid offset from the top left corner - let node_graph_grid_space_offset_top_left = node_graph_to_viewport.inverse().transform_point2(DVec2::ZERO) + DVec2::new(5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64); - - // The inner bound of the import is the highest/furthest left of the two offsets - let top_left_inner_bound = DVec2::new( - node_graph_pixel_offset_top_left.x.min(node_graph_grid_space_offset_top_left.x), - node_graph_pixel_offset_top_left.y.min(node_graph_grid_space_offset_top_left.y), - ); - - let offset_from_top_left = if network - .exports - .first() - .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) - { - DVec2::new(-4. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) - } else { - DVec2::new(-4. * GRID_SIZE as f64, 0.) - }; - - let bounding_box_top_left = DVec2::new((all_nodes_bounding_box[0].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_left; - let import_top_left = DVec2::new(top_left_inner_bound.x.min(bounding_box_top_left.x), top_left_inner_bound.y.min(bounding_box_top_left.y)); - let rounded_import_top_left = DVec2::new((import_top_left.x / 24.).round() * 24., (import_top_left.y / 24.).round() * 24.); - - let viewport_top_right = network_metadata.persistent_metadata.navigation_metadata.node_graph_top_right; - let target_viewport_top_right = DVec2::new( - viewport_top_right.x - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, - viewport_top_right.y + EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64, - ); - - // An offset from the right edge in viewport pixels - let node_graph_pixel_offset_top_right = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_right); - - // A 5x5 grid offset from the right corner - let node_graph_grid_space_offset_top_right = node_graph_to_viewport.inverse().transform_point2(viewport_top_right) + DVec2::new(-5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64); - - // The inner bound of the export is the highest/furthest right of the two offsets - let top_right_inner_bound = DVec2::new( - node_graph_pixel_offset_top_right.x.max(node_graph_grid_space_offset_top_right.x), - node_graph_pixel_offset_top_right.y.min(node_graph_grid_space_offset_top_right.y), - ); - - let offset_from_top_right = if network - .exports - .first() - .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) - { - DVec2::new(2. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) - } else { - DVec2::new(4. * GRID_SIZE as f64, 0.) - }; - - let mut bounding_box_top_right = DVec2::new((all_nodes_bounding_box[1].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.); - bounding_box_top_right += offset_from_top_right; - let export_top_right = DVec2::new(top_right_inner_bound.x.max(bounding_box_top_right.x), top_right_inner_bound.y.min(bounding_box_top_right.y)); - let rounded_export_top_right = DVec2::new((export_top_right.x / 24.).round() * 24., (export_top_right.y / 24.).round() * 24.); - - Some((rounded_import_top_left.as_ivec2(), rounded_export_top_right.as_ivec2())) - } - - /// 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 { - // 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; - } - let (export_type, source) = self.input_type(input_connector, network_path); - let data_type = FrontendGraphDataType::displayed_type(&export_type, &source); - let connected_to = self - .upstream_output_connector(input_connector, network_path) - .map(|output_connector| match output_connector { - OutputConnector::Node { node_id, output_index } => { - let mut name = self.display_name(&node_id, network_path); - if cfg!(debug_assertions) { - name.push_str(&format!(" (id: {node_id})")); - } - format!("{name} output {output_index}") - } - OutputConnector::Import(import_index) => format!("Import index {import_index}"), - }) - .unwrap_or("nothing".to_string()); - - let (name, description) = match input_connector { - InputConnector::Node { node_id, input_index } => self.displayed_input_name_and_description(node_id, *input_index, network_path), - InputConnector::Export(export_index) => { - // Get export name from parent node metadata input, which must match the number of exports. - // Empty string means to use type, or "Export + index" if type is empty determined - let export_name = if network_path.is_empty() { - "Canvas".to_string() - } else { - self.encapsulating_node_metadata(network_path) - .and_then(|encapsulating_metadata| encapsulating_metadata.persistent_metadata.output_names.get(*export_index).cloned()) - .unwrap_or_default() - }; - - let export_name = if !export_name.is_empty() { - export_name - } else if *export_type.nested_type() != concrete!(()) { - export_type.nested_type().to_string() - } else { - format!("Export index {}", export_index) - }; - - (export_name, String::new()) - } - }; - Some(FrontendGraphInput { - data_type, - name, - description, - resolved_type: format!("{export_type:?}"), - valid_types: self.valid_input_types(input_connector, network_path).iter().map(|ty| ty.to_string()).collect(), - connected_to, - }) - } - - /// 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 { - let (output_type, type_source) = self.output_type(output_connector, network_path); - let (name, description) = match output_connector { - OutputConnector::Node { node_id, output_index } => { - // Do not display the primary output port for a node if it is a network node with a hidden primary export - if *output_index == 0 && self.hidden_primary_output(node_id, network_path) { - return None; - }; - // Get the output name from the interior network export name - let node_metadata = self.node_metadata(node_id, network_path)?; - let output_name = node_metadata.persistent_metadata.output_names.get(*output_index).cloned().unwrap_or_default(); - - let output_name = if !output_name.is_empty() { - output_name - } else if *output_type.nested_type() != concrete!(()) { - output_type.nested_type().to_string() - } else { - format!("Output {}", *output_index + 1) - }; - - (output_name, String::new()) - } - OutputConnector::Import(import_index) => { - // Get the import name from the encapsulating node input metadata - let Some((encapsulating_node_id, encapsulating_path)) = network_path.split_last() else { - // Return None if it is an import in the document network - return None; - }; - // Return None if the primary input is hidden and this is the primary import - if *import_index == 0 && self.hidden_primary_import(network_path) { - return None; - }; - let (import_name, description) = self.displayed_input_name_and_description(encapsulating_node_id, *import_index, encapsulating_path); - - let import_name = if *output_type.nested_type() != concrete!(()) { - import_name - } else { - format!("Import index {}", *import_index) - }; - (import_name, description) - } - }; - - let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source); - - let mut connected_to = self - .outward_wires(network_path) - .and_then(|outward_wires| outward_wires.get(output_connector)) - .cloned() - .unwrap_or_else(|| { - log::error!("Could not get {output_connector:?} in outward wires"); - Vec::new() - }) - .iter() - .map(|input| match input { - InputConnector::Node { node_id, input_index } => { - let mut name = self.display_name(node_id, network_path); - if cfg!(debug_assertions) { - name.push_str(&format!(" (id: {node_id})")); - } - format!("{name} input {input_index}") - } - InputConnector::Export(export_index) => format!("Export index {export_index}"), - }) - .collect::>(); - - if connected_to.is_empty() { - connected_to.push("nothing".to_string()); - } - - Some(FrontendGraphOutput { - data_type, - name, - resolved_type: format!("{:?}", output_type), - description, - connected_to, - }) - } - pub fn height_from_click_target(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { let mut node_height: Option = self .node_click_targets(node_id, network_path) @@ -1239,7 +698,7 @@ impl NodeNetworkInterface { }; let description = input_metadata.input_description.to_string(); let name = if input_metadata.input_name.is_empty() { - self.input_type(&InputConnector::node(*node_id, input_index), network_path).0.nested_type().to_string() + self.input_type(&InputConnector::node(*node_id, input_index), network_path).resolved_type_name() } else { input_metadata.input_name.to_string() }; @@ -1279,9 +738,9 @@ impl NodeNetworkInterface { /// Returns the description of the node, or an empty string if it is not set. pub fn description(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { - self.get_node_definition(network_path, *node_id) + self.get_node_definition(node_id, network_path) .map(|node_definition| node_definition.description.to_string()) - .filter(|description| description != "TODO") + .filter(|description: &String| description != "TODO") .unwrap_or_default() } @@ -1317,25 +776,6 @@ impl NodeNetworkInterface { node_metadata.persistent_metadata.is_layer() } - 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 { - 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 { - log::error!("Could not get downstream_connectors in primary_output_connected_to_layer"); - return false; - }; - let downstream_nodes = downstream_connectors.iter().filter_map(|connector| connector.node_id()).collect::>(); - 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 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 @@ -1709,30 +1149,6 @@ impl NodeNetworkInterface { } } -/// Gets the type for a random protonode implementation (used if there is no type from the compiled network) -fn random_protonode_implementation(protonode: &graph_craft::ProtoNodeIdentifier) -> Option<&graphene_std::NodeIOTypes> { - let mut protonode = protonode.clone(); - // TODO: Remove - if let Some((path, _generics)) = protonode.name.split_once('<') { - protonode = path.to_string().to_string().into(); - } - let Some(node_io_hashmap) = NODE_REGISTRY.get(&protonode) else { - log::error!("Could not get hashmap for proto node: {protonode:?}"); - return None; - }; - - let node_types = node_io_hashmap.keys().min_by_key(|node_io_types| { - let mut hasher = DefaultHasher::new(); - node_io_types.hash(&mut hasher); - hasher.finish() - }); - - if node_types.is_none() { - log::error!("Could not get node_types from hashmap"); - }; - node_types -} - // Private mutable getters for use within the network interface impl NodeNetworkInterface { fn network_mut(&mut self, network_path: &[NodeId]) -> Option<&mut NodeNetwork> { @@ -2772,7 +2188,7 @@ impl NodeNetworkInterface { let vector_wire = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style); let path_string = vector_wire.to_svg(); - let data_type = FrontendGraphDataType::from_type(&self.input_type(&input, network_path).0); + let data_type = FrontendGraphDataType::displayed_type(&self.input_type(&input, network_path)); let wire_path_update = Some(WirePath { path_string, data_type, @@ -2810,7 +2226,7 @@ impl NodeNetworkInterface { pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option { let (vector_wire, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?; let path_string = vector_wire.to_svg(); - let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0); + let data_type = FrontendGraphDataType::displayed_type(&self.input_type(input, network_path)); Some(WirePath { path_string, data_type, @@ -3458,15 +2874,7 @@ impl NodeNetworkInterface { let chain_widths = nodes.iter().map(|node_id| (*node_id, self.chain_width(node_id, network_path))).collect::>(); let has_left_input_wire = nodes .iter() - .map(|node_id| { - ( - *node_id, - !self - .upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow) - .skip(1) - .all(|node_id| self.is_chain(&node_id, network_path)), - ) - }) + .map(|node_id| (*node_id, self.layer_has_left_border_gap(node_id, network_path))) .collect::>(); (layer_widths, chain_widths, has_left_input_wire) @@ -4482,7 +3890,7 @@ impl NodeNetworkInterface { } } - let tagged_value = TaggedValue::from_type_or_none(&self.input_type(input_connector, network_path).0); + let tagged_value = self.tagged_value_from_input(input_connector, network_path); let value_input = NodeInput::value(tagged_value, true); @@ -6102,22 +5510,6 @@ impl Iterator for FlowIter<'_> { } } -// TODO: Refactor to be Unknown, Compiled(Type) for NodeInput::Node, or Value(Type) for NodeInput::Value -/// Represents the source of a resolved type (for debugging). -/// There will be two valid types list. One for the current valid types that will not cause a node graph error, -/// based on the other inputs to that node and returned during compilation. THe other list will be all potential -/// Valid types, based on the protonode implementation/downstream users. -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum TypeSource { - Compiled, - RandomProtonodeImplementation, - DocumentNodeDefault, - TaggedValue, - OuterMostExportDefault, - - Error(&'static str), -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum ImportOrExport { Import(usize), diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/node_graph.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/node_graph.rs new file mode 100644 index 0000000000..b1d94d9ab8 --- /dev/null +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/node_graph.rs @@ -0,0 +1,404 @@ +use glam::{DVec2, IVec2}; +use graph_craft::proto::GraphErrors; +use graphene_std::uuid::NodeId; + +use crate::{ + consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP}, + messages::portfolio::document::{ + node_graph::utility_types::{FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput, FrontendNode}, + utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, OutputConnector}, + }, +}; + +// Functions used to collect data from the network interface for use in rendering the node graph +impl NodeNetworkInterface { + pub fn collect_nodes(&mut self, node_graph_errors: &GraphErrors, network_path: &[NodeId]) -> Vec { + let Some(network) = self.nested_network(network_path) else { + log::error!("Could not get nested network when collecting nodes"); + return Vec::new(); + }; + let mut nodes = Vec::new(); + for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::>() { + let node_id_path = [network_path, &[node_id]].concat(); + + let primary_input_connector = InputConnector::node(node_id, 0); + + let primary_input = if self.input_from_connector(&primary_input_connector, network_path).is_some_and(|input| input.is_exposed()) { + self.frontend_input_from_connector(&primary_input_connector, network_path) + } else { + None + }; + let exposed_inputs = (1..self.number_of_inputs(&node_id, network_path)) + .filter_map(|input_index| self.frontend_input_from_connector(&InputConnector::node(node_id, input_index), network_path)) + .collect(); + + let primary_output = self.frontend_output_from_connector(&OutputConnector::node(node_id, 0), network_path); + + let exposed_outputs = (1..self.number_of_outputs(&node_id, network_path)) + .filter_map(|output_index| self.frontend_output_from_connector(&OutputConnector::node(node_id, output_index), network_path)) + .collect(); + let (primary_output_connected_to_layer, primary_input_connected_to_layer) = if self.is_layer(&node_id, network_path) { + ( + self.primary_output_connected_to_layer(&node_id, network_path), + self.primary_input_connected_to_layer(&node_id, network_path), + ) + } else { + (false, false) + }; + + let Some(position) = self.position(&node_id, network_path) else { + log::error!("Could not get position for node: {node_id}"); + continue; + }; + let previewed = self.previewed_node(network_path) == Some(node_id); + + let locked = self.is_locked(&node_id, network_path); + + let errors = node_graph_errors + .iter() + .find(|error| error.node_path == node_id_path) + .map(|error| format!("{:?}", error.error.clone())) + .or_else(|| { + if node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) { + Some("Node graph type error within this node".to_string()) + } else { + None + } + }); + + nodes.push(FrontendNode { + id: node_id, + is_layer: self.node_metadata(&node_id, network_path).is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()), + can_be_layer: self.is_eligible_to_be_layer(&node_id, network_path), + reference: self.reference(&node_id, network_path).cloned().unwrap_or_default(), + display_name: self.display_name(&node_id, network_path), + primary_input, + exposed_inputs, + primary_output, + exposed_outputs, + primary_output_connected_to_layer, + primary_input_connected_to_layer, + position, + previewed, + visible, + locked, + errors, + }); + } + + nodes + } + + /// 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 { + // Return None if it is a hidden input or doesn't exist + if self.input_from_connector(input_connector, network_path).is_some_and(|input| !input.is_exposed()) { + return None; + } + let input_type = self.input_type(input_connector, network_path); + let data_type = FrontendGraphDataType::displayed_type(&input_type); + let resolved_type = input_type.resolved_type_name(); + + let connected_to = self + .upstream_output_connector(input_connector, network_path) + .map(|output_connector| match output_connector { + OutputConnector::Node { node_id, output_index } => { + let mut name = self.display_name(&node_id, network_path); + if cfg!(debug_assertions) { + name.push_str(&format!(" (id: {node_id})")); + } + format!("{name} output {output_index}") + } + OutputConnector::Import(import_index) => format!("Import index {import_index}"), + }) + .unwrap_or("nothing".to_string()); + + let (name, description) = match input_connector { + InputConnector::Node { node_id, input_index } => self.displayed_input_name_and_description(node_id, *input_index, network_path), + InputConnector::Export(export_index) => { + // Get export name from parent node metadata input, which must match the number of exports. + // Empty string means to use type, or "Export + index" if type is empty determined + let export_name = if network_path.is_empty() { + "Canvas".to_string() + } else { + self.encapsulating_node_metadata(network_path) + .and_then(|encapsulating_metadata| encapsulating_metadata.persistent_metadata.output_names.get(*export_index).cloned()) + .unwrap_or_default() + }; + + let export_name = if !export_name.is_empty() { + export_name + } else if let Some(export_type_name) = input_type.compiled_nested_type_name() { + export_type_name + } else { + format!("Export index {}", export_index) + }; + + (export_name, String::new()) + } + }; + + // TODO: Move in separate Tooltip overlay + // let valid_types = match self.valid_input_types(&input_connector, network_path) { + // Ok(input_types) => input_types.iter().map(|ty| ty.to_string()).collect(), + // Err(e) => { + // log::error!("Error getting valid types for input {input_connector:?}: {e}"); + // Vec::new() + // } + // }; + + Some(FrontendGraphInput { + data_type, + resolved_type, + name, + description, + connected_to, + }) + } + + /// 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 { + let output_type = self.output_type(output_connector, network_path); + + let (name, description) = match output_connector { + OutputConnector::Node { node_id, output_index } => { + // Do not display the primary output port for a node if it is a network node with a hidden primary export + if *output_index == 0 && self.hidden_primary_output(node_id, network_path) { + return None; + }; + // Get the output name from the interior network export name + let node_metadata = self.node_metadata(node_id, network_path)?; + let output_name = node_metadata.persistent_metadata.output_names.get(*output_index).cloned().unwrap_or_default(); + + let output_name = if !output_name.is_empty() { output_name } else { output_type.resolved_type_name() }; + (output_name, String::new()) + } + OutputConnector::Import(import_index) => { + // Get the import name from the encapsulating node input metadata + let Some((encapsulating_node_id, encapsulating_path)) = network_path.split_last() else { + // Return None if it is an import in the document network + return None; + }; + // Return None if the primary input is hidden and this is the primary import + if *import_index == 0 && self.hidden_primary_import(network_path) { + return None; + }; + let (import_name, description) = self.displayed_input_name_and_description(encapsulating_node_id, *import_index, encapsulating_path); + + let import_name = if !import_name.is_empty() { + import_name + } else if let Some(import_type_name) = output_type.compiled_nested_type_name() { + import_type_name + } else { + format!("Import index {}", *import_index) + }; + + (import_name, description) + } + }; + let data_type = FrontendGraphDataType::displayed_type(&output_type); + let resolved_type = output_type.resolved_type_name(); + let mut connected_to = self + .outward_wires(network_path) + .and_then(|outward_wires| outward_wires.get(output_connector)) + .cloned() + .unwrap_or_else(|| { + log::error!("Could not get {output_connector:?} in outward wires"); + Vec::new() + }) + .iter() + .map(|input| match input { + InputConnector::Node { node_id, input_index } => { + let mut name = self.display_name(node_id, network_path); + if cfg!(debug_assertions) { + name.push_str(&format!(" (id: {node_id})")); + } + format!("{name} input {input_index}") + } + InputConnector::Export(export_index) => format!("Export index {export_index}"), + }) + .collect::>(); + + if connected_to.is_empty() { + connected_to.push("nothing".to_string()); + } + + Some(FrontendGraphOutput { + data_type, + resolved_type, + name, + description, + connected_to, + }) + } + + 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 = 0u32; + // 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 * 7 + 1; + } + } + + last_chain_node_distance * 7 + 1 + } else { + // Layer with no inputs has no chain + 0 + } + } + + /// Checks if a layer should display a gap in its left border + pub fn layer_has_left_border_gap(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { + self.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow) + .skip(1) + .any(|node_id| !self.is_chain(&node_id, network_path)) + } + + /// Returns the node which should have a dashed border drawn around it + pub fn previewed_node(&self, network_path: &[NodeId]) -> Option { + self.upstream_output_connector(&InputConnector::Export(0), network_path) + .and_then(|output_connector| output_connector.node_id()) + .filter(|output_node| self.root_node(network_path).is_some_and(|root_node| root_node.node_id != *output_node)) + } + + /// If any downstream input are bottom layer inputs, then the thick cap should be displayed above the output port + 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 { + 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 { + log::error!("Could not get downstream_connectors in primary_output_connected_to_layer"); + return false; + }; + let downstream_nodes = downstream_connectors + .iter() + .filter_map(|connector| if connector.input_index() == 0 { connector.node_id() } else { None }) + .collect::>(); + downstream_nodes.iter().any(|node_id| self.is_layer(node_id, network_path)) + } + + /// If any upstream nodes are layers, then the thick cap should be displayed below the primary input port + 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 frontend_imports(&mut self, network_path: &[NodeId]) -> Vec> { + match network_path.split_last() { + Some((node_id, encapsulatingnetwork_path)) => { + let Some(node) = self.document_node(node_id, encapsulatingnetwork_path) else { + log::error!("Could not get node {node_id} in network {encapsulatingnetwork_path:?}"); + return Vec::new(); + }; + let mut frontend_imports = (0..node.inputs.len()) + .map(|import_index| self.frontend_output_from_connector(&OutputConnector::Import(import_index), network_path)) + .collect::>(); + if frontend_imports.is_empty() { + frontend_imports.push(None); + } + frontend_imports + } + // In the document network display no imports + None => Vec::new(), + } + } + + pub fn frontend_exports(&mut 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() { + frontend_exports.push(None); + } + 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 { + log::error!("Could not get all nodes bounding box in load_export_ports"); + return None; + }; + let Some(network) = self.nested_network(network_path) else { + log::error!("Could not get current network in load_export_ports"); + return None; + }; + + let Some(network_metadata) = self.network_metadata(network_path) else { + log::error!("Could not get nested network_metadata in load_export_ports"); + return None; + }; + let node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport; + let target_viewport_top_left = DVec2::new(IMPORTS_TO_LEFT_EDGE_PIXEL_GAP as f64, IMPORTS_TO_TOP_EDGE_PIXEL_GAP as f64); + + let node_graph_pixel_offset_top_left = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_left); + + // A 5x5 grid offset from the top left corner + let node_graph_grid_space_offset_top_left = node_graph_to_viewport.inverse().transform_point2(DVec2::ZERO) + DVec2::new(5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64); + + // The inner bound of the import is the highest/furthest left of the two offsets + let top_left_inner_bound = DVec2::new( + node_graph_pixel_offset_top_left.x.min(node_graph_grid_space_offset_top_left.x), + node_graph_pixel_offset_top_left.y.min(node_graph_grid_space_offset_top_left.y), + ); + + let offset_from_top_left = if network + .exports + .first() + .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) + { + DVec2::new(-4. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) + } else { + DVec2::new(-4. * GRID_SIZE as f64, 0.) + }; + + let bounding_box_top_left = DVec2::new((all_nodes_bounding_box[0].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_left; + let import_top_left = DVec2::new(top_left_inner_bound.x.min(bounding_box_top_left.x), top_left_inner_bound.y.min(bounding_box_top_left.y)); + let rounded_import_top_left = DVec2::new((import_top_left.x / 24.).round() * 24., (import_top_left.y / 24.).round() * 24.); + + let viewport_top_right = network_metadata.persistent_metadata.navigation_metadata.node_graph_top_right; + let target_viewport_top_right = DVec2::new( + viewport_top_right.x - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, + viewport_top_right.y + EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64, + ); + + // An offset from the right edge in viewport pixels + let node_graph_pixel_offset_top_right = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_right); + + // A 5x5 grid offset from the right corner + let node_graph_grid_space_offset_top_right = node_graph_to_viewport.inverse().transform_point2(viewport_top_right) + DVec2::new(-5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64); + + // The inner bound of the export is the highest/furthest right of the two offsets + let top_right_inner_bound = DVec2::new( + node_graph_pixel_offset_top_right.x.max(node_graph_grid_space_offset_top_right.x), + node_graph_pixel_offset_top_right.y.min(node_graph_grid_space_offset_top_right.y), + ); + + let offset_from_top_right = if network + .exports + .first() + .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) + { + DVec2::new(2. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) + } else { + DVec2::new(4. * GRID_SIZE as f64, 0.) + }; + + let mut bounding_box_top_right = DVec2::new((all_nodes_bounding_box[1].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.); + bounding_box_top_right += offset_from_top_right; + let export_top_right = DVec2::new(top_right_inner_bound.x.max(bounding_box_top_right.x), top_right_inner_bound.y.min(bounding_box_top_right.y)); + let rounded_export_top_right = DVec2::new((export_top_right.x / 24.).round() * 24., (export_top_right.y / 24.).round() * 24.); + + Some((rounded_import_top_left.as_ivec2(), rounded_export_top_right.as_ivec2())) + } +} 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 new file mode 100644 index 0000000000..1e099c7ca1 --- /dev/null +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -0,0 +1,343 @@ +use std::collections::{HashMap, HashSet}; + +use graph_craft::{ + ProtoNodeIdentifier, Type, concrete, + document::{DocumentNodeImplementation, InlineRust, NodeInput, value::TaggedValue}, +}; +use graphene_std::uuid::NodeId; +use interpreted_executor::{ + dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta}, + node_registry::NODE_REGISTRY, +}; + +use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector}; + +// This file contains utility methods for interfacing with the resolved types returned from the compiler +#[derive(Debug, Default)] +pub struct ResolvedDocumentNodeTypes { + pub types: HashMap, NodeTypes>, +} + +impl ResolvedDocumentNodeTypes { + pub fn update(&mut self, delta: ResolvedDocumentNodeTypesDelta) { + for (path, node_type) in delta.add { + self.types.insert(path.to_vec(), node_type); + } + for path in delta.remove { + self.types.remove(&path.to_vec()); + } + } +} + +/// Represents the result of a type query for an input or output connector. +#[derive(Debug, Clone, PartialEq)] +pub enum TypeSource { + // A type that has been compiled based on all upstream types + Compiled(Type), + // The type of value inputs + TaggedValue(Type), + // A type that is guessed from the document node definition + DocumentNodeDefinition(Type), + // When the input is not compiled, the type is unknown and must be guessed from the valid types + Unknown, + + Error(&'static str), +} + +impl TypeSource { + pub fn into_compiled_nested_type(self) -> Option { + match self { + TypeSource::Compiled(compiled_type) => Some(compiled_type.into_nested_type()), + TypeSource::TaggedValue(value_type) => Some(value_type.into_nested_type()), + _ => None, + } + } + + pub fn compiled_nested_type(&self) -> Option<&Type> { + match self { + TypeSource::Compiled(compiled_type) => Some(compiled_type.nested_type()), + TypeSource::TaggedValue(value_type) => Some(value_type.nested_type()), + _ => None, + } + } + + // If Some, the type should be displayed in the imports/exports, if None it should be replaced with "import/export index _" + pub fn compiled_nested_type_name(&self) -> Option { + self.compiled_nested_type().map(|ty| ty.to_string()) + } + + // Used when searching for nodes in the add Node popup + pub fn add_node_string(&self) -> Option { + self.compiled_nested_type().map(|ty| format!("type:{}", ty.to_string())) + } + + // The type to display in the tooltip + pub fn resolved_type_name(&self) -> String { + match self { + TypeSource::Compiled(compiled_type) => compiled_type.nested_type().to_string(), + TypeSource::TaggedValue(value_type) => value_type.nested_type().to_string(), + TypeSource::DocumentNodeDefinition(_) => "Unknown".to_string(), + TypeSource::Unknown => "Unknown".to_string(), + TypeSource::Error(_) => "Error".to_string(), + } + } +} + +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 { + let Some(input) = self.input_from_connector(input_connector, network_path) else { + return TypeSource::Error("Could not get input from connector"); + }; + + match input { + NodeInput::Node { node_id, output_index } => { + let input_type = self.output_type(&OutputConnector::node(*node_id, *output_index), network_path); + if input_type == TypeSource::Unknown { + // If we are trying to get the input type of an unknown node, check if it has a reference to its definition and use that input type + if let InputConnector::Node { node_id, input_index } = input_connector { + if let Some(definition) = self.get_node_definition(node_id, network_path) { + if let Some(ty) = definition + .node_template + .document_node + .inputs + .get(*input_index) + .cloned() + .and_then(|input| input.as_value().map(|value| value.ty())) + { + return TypeSource::DocumentNodeDefinition(ty); + } + } + } + } + input_type + } + NodeInput::Value { tagged_value, .. } => TypeSource::TaggedValue(tagged_value.ty()), + NodeInput::Network { import_index, .. } => { + // Get the input type of the encapsulating node input + let Some((encapsulating_node, encapsulating_path)) = network_path.split_last() else { + return TypeSource::Error("Could not get type of import in document network"); + }; + self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path) + } + NodeInput::Scope(_) => TypeSource::Compiled(concrete!(())), + NodeInput::Reflection(document_node_metadata) => TypeSource::Compiled(document_node_metadata.ty()), + NodeInput::Inline(_) => TypeSource::Compiled(concrete!(InlineRust)), + } + } + + // 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 { + let guaranteed_type = match self.input_type(input_connector, network_path) { + TypeSource::Compiled(compiled) => compiled, + TypeSource::TaggedValue(value) => value, + TypeSource::DocumentNodeDefinition(definition) => definition, + TypeSource::Unknown => { + let mut valid_types = match self.valid_input_types(input_connector, network_path) { + Ok(types) => types, + Err(e) => { + log::error!("Error getting valid_input_types for {input_connector:?}: {e}"); + Vec::new() + } + }; + match valid_types.pop() { + Some(valid_type) => valid_type, + None => { + match self.random_downstream_type_from_connector(input_connector, network_path) { + Some(random_type) => random_type, + // If there are no connected protonodes then we give up and return the empty type + None => concrete!(()), + } + } + } + } + TypeSource::Error(e) => { + log::error!("Error getting tagged_value_from_input for {input_connector:?} {e}"); + concrete!(()) + } + }; + TaggedValue::from_type_or_none(&guaranteed_type) + } + + pub fn valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Result, String> { + match input_connector { + InputConnector::Node { node_id, input_index } => { + let Some(implementation) = self.implementation(node_id, network_path) else { + return Err(format!("Could not get node implementation for {:?} {} in valid_input_types", network_path, *node_id)); + }; + match implementation { + DocumentNodeImplementation::Network(_) => self.valid_output_types(&OutputConnector::Import(input_connector.input_index()), &[network_path, &[*node_id]].concat()), + DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { + let Some(implementations) = NODE_REGISTRY.get(proto_node_identifier) else { + return Err(format!("Protonode {proto_node_identifier:?} not found in registry")); + }; + let valid_output_types = match self.valid_output_types(&OutputConnector::node(*node_id, 0), network_path) { + Ok(valid_types) => valid_types, + Err(e) => return Err(e), + }; + + let valid_types = implementations + .iter() + .filter_map(|(node_io, _)| { + if !valid_output_types.iter().any(|output_type| output_type.nested_type() == node_io.return_value.nested_type()) { + return None; + } + + let valid_inputs = (0..node_io.inputs.len()).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| { + let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path); + match input_type.into_compiled_nested_type() { + Some(input_type) => node_io.inputs.get(iterator_index).map(|input_type| input_type.nested_type()) == Some(&input_type), + None => true, + } + }); + if valid_inputs { node_io.inputs.get(*input_index).cloned() } else { None } + }) + .collect::>(); + Ok(valid_types) + } + DocumentNodeImplementation::Extract => { + log::error!("Input types for extract node not supported"); + Ok(Vec::new()) + } + } + } + InputConnector::Export(export_index) => { + match network_path.split_last() { + Some((encapsulating_node, encapsulating_path)) => self.valid_output_types(&OutputConnector::node(*encapsulating_node, *export_index), encapsulating_path), + None => { + // Valid types for the export are all types that can be fed into the render node + // TODO: Use ::IDENTIFIER + let render_node = "graphene_std::wasm_application_io::RenderNode"; + let Some(implementations) = NODE_REGISTRY.get(&ProtoNodeIdentifier::new(render_node)) else { + return Err(format!("Protonode {render_node:?} not found in registry")); + }; + Ok(implementations.iter().map(|(types, _)| types.inputs[1].clone()).collect()) + } + } + } + } + } + + /// Retrieves the output types for a given document node and its exports. + /// + /// This function traverses the node and its nested network structure (if applicable) to determine + /// the type of the output + /// + /// # Arguments + /// + /// * `node` - A reference to the `DocumentNode` for which to determine output types. + /// * `resolved_types` - A reference to `ResolvedDocumentNodeTypes` containing pre-resolved type information. + /// * `node_id_path` - A slice of `NodeId`s representing the path to the current node in the document graph. + /// + /// # Behavior + /// + /// 1. Retrieves the primary output type from `resolved_types`. + /// 2. If the node is a network: + /// - Iterates through its exports (skipping the first/primary export). + /// - For each export, traverses the network until reaching a protonode or terminal condition. + /// - Determines the output type based on the final node/value encountered. + /// 3. Collects and returns all resolved types. + /// + pub fn output_type(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> TypeSource { + match output_connector { + OutputConnector::Node { node_id, output_index } => { + // First try iterating upstream to the first protonode and try get its compiled type + let Some(implementation) = self.implementation(node_id, network_path) else { + return TypeSource::Error("Could not get implementation"); + }; + match implementation { + DocumentNodeImplementation::Network(_) => self.input_type(&InputConnector::Export(*output_index), &[network_path, &[*node_id]].concat()), + DocumentNodeImplementation::ProtoNode(_) => match self.resolved_types.types.get(&[network_path, &[*node_id]].concat()) { + Some(resolved_type) => TypeSource::Compiled(resolved_type.output.clone()), + None => TypeSource::Unknown, + }, + DocumentNodeImplementation::Extract => TypeSource::Compiled(concrete!(())), + } + } + OutputConnector::Import(import_index) => { + let Some((encapsulating_node, encapsulating_path)) = network_path.split_last() else { + return TypeSource::Error("Cannot get import type in document network"); + }; + self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path) + } + } + } + // The valid output types are all types that are valid for each downstream connection + pub fn valid_output_types(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Result, String> { + let Some(outward_wires) = self.outward_wires(&network_path) else { + return Err("Could not get outward wires in valid_input_types".to_string()); + }; + let Some(inputs_from_import) = outward_wires.get(output_connector) else { + return Err("Could not get inputs from import in valid_input_types".to_string()); + }; + + let intersection = inputs_from_import + .clone() + .iter() + .filter_map(|input_connector| match self.valid_input_types(input_connector, &network_path) { + Ok(valid_types) => Some(valid_types), + Err(e) => { + log::error!("Error getting valid types in intersection: {e}"); + None + } + }) + .map(|vec| vec.into_iter().collect::>()) + .fold(None, |acc: Option>, set| match acc { + Some(acc_set) => Some(acc_set.intersection(&set).cloned().collect()), + None => Some(set), + }) + .unwrap_or_default(); + + Ok(intersection.into_iter().collect::>()) + } + + pub fn random_downstream_type_from_connector(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { + match input_connector { + InputConnector::Node { node_id, input_index } => { + let Some(implementation) = self.implementation(node_id, network_path) else { + log::error!("Could not get node {node_id} in random_downstream_protonode_from_connector"); + return None; + }; + match implementation { + DocumentNodeImplementation::Network(_) => { + let Some(outward_wires) = self.outward_wires(&network_path) else { + log::error!("Could not get outward wires in random_downstream_protonode_from_connector"); + return None; + }; + let Some(inputs_from_import) = outward_wires.get(&OutputConnector::Import(*input_index)) else { + log::error!("Could not get inputs from import in valid_input_types"); + return None; + }; + let Some(first_input) = inputs_from_import.first().cloned() else { + return None; + }; + self.random_downstream_type_from_connector(&first_input, &[network_path, &[*node_id]].concat()) + } + DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { + let Some(implementations) = NODE_REGISTRY.get(proto_node_identifier) else { + log::error!("Protonode {proto_node_identifier:?} not found in registry"); + return None; + }; + implementations.keys().next().and_then(|node_io| node_io.inputs.get(input_connector.input_index())).cloned() + } + DocumentNodeImplementation::Extract => None, + } + } + InputConnector::Export(export_index) => network_path.split_last().and_then(|(encapsulating_node, encapsulating_path)| { + let Some(outward_wires) = self.outward_wires(&encapsulating_path) else { + log::error!("Could not get outward wires in random_downstream_protonode_from_connector export"); + return None; + }; + let Some(inputs_from_import) = outward_wires.get(&OutputConnector::node(*encapsulating_node, *export_index)) else { + log::error!("Could not get inputs from import in valid_input_types"); + return None; + }; + let Some(first_input) = inputs_from_import.first().cloned() else { + return None; + }; + self.random_downstream_type_from_connector(&first_input, encapsulating_path) + }), + } + } +} diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 4cde2d08d6..fcb670c2d6 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -475,8 +475,8 @@ impl<'a> NodeGraphLayer<'a> { /// Check if a layer is a raster layer pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool { - let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).0.nested_type().clone(); + let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).into_compiled_nested_type(); - layer_input_type == concrete!(Table>) || layer_input_type == concrete!(Table>) + layer_input_type == Some(concrete!(Table>)) || layer_input_type == Some(concrete!(Table>)) } } diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 4d0a268449..9eaa3dd1e3 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -581,8 +581,8 @@ pub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterfac // Must be a layer of type Table let node_id = NodeGraphLayer::new(first_layer, network_interface).horizontal_layer_flow().nth(1)?; - let (output_type, _) = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]); - if output_type.nested_type() != concrete!(Table).nested_type() { + let output_type = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]); + if output_type.into_compiled_nested_type() != Some(concrete!(Table)) { return None; } diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 1c0dea12e9..ad1497d36d 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -176,14 +176,22 @@ return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`; } + function inputTooltip(value: FrontendGraphInput): string { + return dataTypeTooltip(value) + "\n\n" + inputConnectedToText(value) + "\n\n"; + } + + function outputTooltip(value: FrontendGraphOutput): string { + return dataTypeTooltip(value) + "\n\n" + outputConnectedToText(value); + } + function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string { return `Data Type: ${value.resolvedType}`; } - function validTypesText(value: FrontendGraphInput): string { - const validTypes = value.validTypes.length > 0 ? value.validTypes.map((x) => `• ${x}`).join("\n") : "None"; - return `Valid Types:\n${validTypes}`; - } + // function validTypesText(value: FrontendGraphInput): string { + // const validTypes = value.validTypes.length > 0 ? value.validTypes.map((x) => `• ${x}`).join("\n") : "None"; + // return `Valid Types:\n${validTypes}`; + // } function outputConnectedToText(output: FrontendGraphOutput): string { if (output.connectedTo.length === 0) return "Connected to nothing"; @@ -317,7 +325,7 @@ style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24} style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index} > - {`${dataTypeTooltip(frontendOutput)}\n\n${outputConnectedToText(frontendOutput)}`} + {outputTooltip(frontendOutput)} {#if frontendOutput.connectedTo.length > 0} {:else} @@ -389,7 +397,7 @@ style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 8) / 24} style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 8) / 24 + index} > - {`${dataTypeTooltip(frontendInput)}\n\n${inputConnectedToText(frontendInput)}`} + {inputTooltip(frontendInput)} {#if frontendInput.connectedTo !== "nothing"} {:else} @@ -528,7 +536,7 @@ style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`} style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`} > - {`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`} + {outputTooltip(node.primaryOutput)} {#if node.primaryOutput.connectedTo.length > 0} {#if node.primaryOutputConnectedToLayer} @@ -550,7 +558,7 @@ style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`} > {#if node.primaryInput} - {`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`} + {inputTooltip(node.primaryInput)} {/if} {#if node.primaryInput?.connectedTo !== "nothing"} @@ -574,7 +582,7 @@ style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`} style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`} > - {`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`} + {inputTooltip(stackDataInput)} {#if stackDataInput.connectedTo !== undefined} {:else} @@ -692,7 +700,7 @@ style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`} style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`} > - {`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`} + {inputTooltip(node.primaryInput)} {#if node.primaryInput.connectedTo !== undefined} {:else} @@ -711,7 +719,7 @@ style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`} style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`} > - {`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`} + {inputTooltip(secondary)} {#if secondary.connectedTo !== undefined} {:else} @@ -733,7 +741,7 @@ style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`} style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`} > - {`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`} + {outputTooltip(node.primaryOutput)} {#if node.primaryOutput.connectedTo !== undefined} {:else} @@ -751,7 +759,7 @@ style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`} style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`} > - {`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`} + {outputTooltip(secondary)} {#if secondary.connectedTo !== undefined} {:else} diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 5d1f30a6bc..2648f5137d 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -197,8 +197,6 @@ export class FrontendGraphInput { readonly resolvedType!: string; - readonly validTypes!: string[]; - readonly connectedTo!: string; } diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index b95b869496..133cb88c1d 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -334,6 +334,15 @@ impl Type { } } + pub fn into_nested_type(self) -> Type { + match self { + Self::Generic(_) => self, + Self::Concrete(_) => self, + Self::Fn(_, output) => output.into_nested_type(), + Self::Future(output) => output.into_nested_type(), + } + } + pub fn replace_nested(&mut self, f: impl Fn(&Type) -> Option) -> Option { if let Some(replacement) = f(self) { return Some(std::mem::replace(self, replacement)); diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 79582b7fc8..1686e571ca 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -204,6 +204,14 @@ pub enum DocumentNodeMetadata { DocumentNodePath, } +impl DocumentNodeMetadata { + pub fn ty(&self) -> Type { + match self { + DocumentNodeMetadata::DocumentNodePath => concrete!(Vec), + } + } +} + impl NodeInput { pub const fn node(node_id: NodeId, output_index: usize) -> Self { Self::Node { node_id, output_index } diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 2f52214ed7..53f23fa50f 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -33,25 +33,21 @@ impl Default for DynamicExecutor { } } -#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct NodeTypes { - pub inputs: Vec, - pub output: Type, -} - -#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct ResolvedDocumentNodeTypes { - pub types: HashMap, NodeTypes>, -} - type Path = Box<[NodeId]>; -#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] pub struct ResolvedDocumentNodeTypesDelta { pub add: Vec<(Path, NodeTypes)>, pub remove: Vec, } +#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct NodeTypes { + // This is currently unused. Only the output is used + pub inputs: Vec, + pub output: Type, +} + impl DynamicExecutor { pub async fn new(proto_network: ProtoNetwork) -> Result { let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY); @@ -111,8 +107,6 @@ impl DynamicExecutor { pub fn document_node_types<'a>(&'a self, nodes: impl Iterator + 'a) -> impl Iterator + 'a { nodes.flat_map(|id| self.tree.source_map().get(&id).map(|(_, b)| (id, b.clone()))) - // TODO: https://github.com/GraphiteEditor/Graphite/issues/1767 - // TODO: Non exposed inputs are not added to the inputs_source_map, so they are not included in the resolved_document_node_types. The type is still available in the typing_context. This only affects the UI-only "Import" node. } } diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index 28d77e75e7..d5c4d0a90b 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -42,6 +42,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc