diff --git a/.github/workflows/clippy-warnings-comment.yaml b/.github/workflows/clippy-warnings-comment.yaml index fe4d3d33..2abc0b06 100644 --- a/.github/workflows/clippy-warnings-comment.yaml +++ b/.github/workflows/clippy-warnings-comment.yaml @@ -24,8 +24,10 @@ jobs: override: true components: clippy - - name: Install Deps - run: sudo apt-get install libgtk-3-dev libsoup2.4-dev libjavascriptcoregtk-4.0-dev libwebkit2gtk-4.0-dev + - name: Install deps + run: | + sudo apt update + sudo apt install libgtk-3-dev libsoup2.4-dev libjavascriptcoregtk-4.0-dev libwebkit2gtk-4.0-dev - name: Run Clippy id: clippy 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 0722b302..0ac102a5 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 @@ -6,7 +6,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext; use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, NodeTemplate, OutputConnector, Previewing}; +use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, NodeTemplate, OutputConnector, Previewing, TypeSource}; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; @@ -181,7 +181,7 @@ impl<'a> MessageHandler> for NodeGrap .find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty())) { responses.add(NodeGraphMessage::CreateWire { - output_connector: output_connector.clone(), + output_connector: *output_connector, input_connector: InputConnector::node(node_id, input_index), }); @@ -481,7 +481,7 @@ impl<'a> MessageHandler> for NodeGrap if let Some(clicked_input) = &clicked_input { responses.add(DocumentMessage::StartTransaction); self.initial_disconnecting = true; - self.disconnecting = Some(clicked_input.clone()); + self.disconnecting = Some(*clicked_input); let output_connector = if *clicked_input == InputConnector::Export(0) { network_interface.root_node(selection_network_path).map(|root_node| root_node.to_connector()) @@ -615,9 +615,7 @@ impl<'a> MessageHandler> for NodeGrap if disconnect_root_node { responses.add(NodeGraphMessage::DisconnectRootNode); } else { - responses.add(NodeGraphMessage::DisconnectInput { - input_connector: disconnecting.clone(), - }); + responses.add(NodeGraphMessage::DisconnectInput { input_connector: *disconnecting }); } // Update the frontend that the node is disconnected responses.add(NodeGraphMessage::RunDocumentGraph); @@ -781,8 +779,8 @@ impl<'a> MessageHandler> for NodeGrap if let (Some(output_connector), Some(input_connector)) = (&output_connector, &input_connector) { responses.add(NodeGraphMessage::CreateWire { - input_connector: input_connector.clone(), - output_connector: output_connector.clone(), + input_connector: *input_connector, + output_connector: *output_connector, }); responses.add(NodeGraphMessage::RunDocumentGraph); @@ -972,7 +970,7 @@ impl<'a> MessageHandler> for NodeGrap { responses.add(NodeGraphMessage::InsertNodeBetween { node_id: selected_node_id, - input_connector: overlapping_wire.wire_end.clone(), + input_connector: overlapping_wire.wire_end, insert_node_input_index: selected_node_input_index, }); @@ -1650,6 +1648,7 @@ impl NodeGraphMessageHandler { log::error!("Could not get nested network when collecting nodes"); return Vec::new(); }; + for node_id in network.nodes.keys().cloned().collect::>() { if network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path) { can_be_layer_lookup.insert(node_id); @@ -1660,6 +1659,7 @@ impl NodeGraphMessageHandler { log::error!("Could not get position for node {node_id}"); } } + let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface); let Some(network) = network_interface.network(breadcrumb_network_path) else { log::error!("Could not get nested network when collecting nodes"); return Vec::new(); @@ -1671,63 +1671,24 @@ impl NodeGraphMessageHandler { let mut nodes = Vec::new(); for (&node_id, node) in &network.nodes { - let node_id_path = &[breadcrumb_network_path, (&[node_id])].concat(); - let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else { - log::error!("Could not get node_metadata for {node_id_path:?}"); - continue; - }; + let node_id_path = [breadcrumb_network_path, (&[node_id])].concat(); - let frontend_graph_inputs = node.inputs.iter().enumerate().map(|(index, _)| { - // Convert the index in all inputs to the index in only the exposed inputs - // TODO: Only display input type if potential inputs in node_registry are all the same type - let node_type = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path); - // TODO: Should display the color of the "most commonly relevant" (we'd need some sort of precedence) data type it allows given the current generic form that's constrained by the other present connections. - let data_type = FrontendGraphDataType::with_type(&node_type); - - let input_name = node_metadata - .persistent_metadata - .input_names - .get(index) - .cloned() - .unwrap_or(network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path).nested_type().to_string()); - - FrontendGraphInput { - data_type, - name: input_name, - resolved_type: Some(format!("{:?}", node_type)), - connected_to: None, - } - }); - - let mut inputs = node.inputs.iter().zip(frontend_graph_inputs).map(|(node_input, mut frontend_graph_input)| { - if let NodeInput::Node { - node_id: connected_node_id, - output_index, - .. - } = node_input - { - frontend_graph_input.connected_to = Some(OutputConnector::node(*connected_node_id, *output_index)); - } else if let NodeInput::Network { import_index, .. } = node_input { - frontend_graph_input.connected_to = Some(OutputConnector::Import(*import_index)); - } - (node_input, frontend_graph_input) - }); - - let primary_input = inputs - .next() - .filter(|(input, _)| { - // Don't show EditorApi input to nodes like "Text" in the document network - input.is_exposed_to_frontend(breadcrumb_network_path.is_empty()) + let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default(); + let mut inputs = inputs.into_iter().map(|input| { + input.map(|input| FrontendGraphInput { + data_type: FrontendGraphDataType::with_type(&input.ty), + resolved_type: Some(format!("{:?} from {:?}", &input.ty, input.type_source)), + name: input.name.unwrap_or_else(|| input.ty.nested_type().to_string()), + connected_to: input.output_connector, }) - .map(|(_, input_type)| input_type); - let exposed_inputs = inputs - .filter(|(input, _)| input.is_exposed_to_frontend(breadcrumb_network_path.is_empty())) - .map(|(_, input_type)| input_type) - .collect(); + }); + + let primary_input = inputs.next().flatten(); + let exposed_inputs = inputs.flatten().collect(); let output_types = network_interface.output_types(&node_id, breadcrumb_network_path); let primary_output_type = output_types.first().expect("Primary output should always exist"); - let frontend_data_type = if let Some(output_type) = primary_output_type { + let frontend_data_type = if let Some((output_type, _)) = primary_output_type { FrontendGraphDataType::with_type(output_type) } else { FrontendGraphDataType::General @@ -1737,7 +1698,7 @@ impl NodeGraphMessageHandler { Some(FrontendGraphOutput { data_type: frontend_data_type, name: "Output 1".to_string(), - resolved_type: primary_output_type.clone().map(|input| format!("{input:?}")), + resolved_type: primary_output_type.clone().map(|(input, type_source)| format!("{input:?} from {type_source:?}")), connected_to, }) } else { @@ -1749,7 +1710,7 @@ impl NodeGraphMessageHandler { if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) { continue; } - let frontend_data_type = if let Some(output_type) = &exposed_output { + let frontend_data_type = if let Some((output_type, _)) = &exposed_output { FrontendGraphDataType::with_type(output_type) } else { FrontendGraphDataType::General @@ -1769,7 +1730,7 @@ impl NodeGraphMessageHandler { exposed_outputs.push(FrontendGraphOutput { data_type: frontend_data_type, name: output_name, - resolved_type: exposed_output.clone().map(|input| format!("{input:?}")), + resolved_type: exposed_output.clone().map(|(input, type_source)| format!("{input:?} from {type_source:?}")), connected_to, }); } @@ -1791,10 +1752,10 @@ impl NodeGraphMessageHandler { let errors = self .node_graph_errors .iter() - .find(|error| error.node_path == *node_id_path) + .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)) { + 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 @@ -1985,6 +1946,64 @@ impl NodeGraphMessageHandler { } } +#[derive(Default)] +struct InputLookup { + name: Option, + ty: Type, + type_source: TypeSource, + output_connector: Option, +} + +type FrontendInputsLookup = HashMap>>; + +/// Create a lookup hashmap that can be used to create the frontend inputs. This is needed because `input_type` requires a mutable `network_interface`. +fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: &mut NodeNetworkInterface) -> FrontendInputsLookup { + let Some(network) = network_interface.network(breadcrumb_network_path) else { + return Default::default(); + }; + let network_metadata = network_interface.network_metadata(breadcrumb_network_path); + let mut frontend_inputs_lookup = HashMap::new(); + for (&node_id, node) in network.nodes.iter() { + let node_metadata = network_metadata.and_then(|network_metadata| network_metadata.persistent_metadata.node_metadata.get(&node_id)); + let mut inputs = Vec::with_capacity(node.inputs.len()); + for (index, input) in node.inputs.iter().enumerate() { + let is_exposed = input.is_exposed_to_frontend(breadcrumb_network_path.is_empty()); + + // Skip not exposed inputs (they still get an entry to help with finding the primary input) + if !is_exposed { + inputs.push(None); + continue; + } + + // Get the name from the metadata here (since it also requires a reference to the `network_interface`) + let name = node_metadata.and_then(|node_metadata| node_metadata.persistent_metadata.input_names.get(index)).cloned(); + + // Get the output connector that feeds into this input (done here as well for simplicity) + let connector = OutputConnector::from_input(input); + + inputs.push(Some(InputLookup { + name, + output_connector: connector, + ..Default::default() + })); + } + frontend_inputs_lookup.insert(node_id, inputs); + } + + for (&node_id, value) in frontend_inputs_lookup.iter_mut() { + for (index, value) in value.iter_mut().enumerate() { + // Skip not exposed inputs for efficiency + let Some(value) = value else { continue }; + + // Resolve the type (done in a seperate loop because it requires a mutable reference to the `network_interface`) + let (ty, type_source) = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path); + value.ty = ty; + value.type_source = type_source; + } + } + frontend_inputs_lookup +} + impl Default for NodeGraphMessageHandler { fn default() -> Self { let right_side_widgets = vec![ 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 575bc6df..012d6e2d 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -3,6 +3,7 @@ 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::{resolve_document_node_type, DocumentNodeDefinition}; use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; use crate::messages::tool::common_functionality::graph_modification_utils; @@ -170,7 +171,7 @@ impl NodeNetworkInterface { layers } - pub fn chain_width(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> u32 { + pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 { if self.number_of_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 @@ -413,7 +414,7 @@ impl NodeNetworkInterface { /// Converts all node id inputs to a new id based on a HashMap. /// /// If the node is not in the hashmap then a default input is found based on the compiled network, using the node_id passed as a parameter - pub fn map_ids(&self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap, network_path: &[NodeId]) -> NodeTemplate { + pub fn map_ids(&mut self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap, network_path: &[NodeId]) -> NodeTemplate { for (input_index, input) in node_template.document_node.inputs.iter_mut().enumerate() { if let &mut NodeInput::Node { node_id: id, output_index, lambda } = input { if let Some(&new_id) = new_ids.get(&id) { @@ -424,12 +425,12 @@ impl NodeNetworkInterface { }; } else { // Disconnect node input if it is not connected to another node in new_ids - let tagged_value = TaggedValue::from_type(&self.input_type(&InputConnector::node(*node_id, input_index), network_path)); + let tagged_value = TaggedValue::from_type(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0); *input = NodeInput::value(tagged_value, true); } } else if let &mut NodeInput::Network { .. } = input { // Always disconnect network node input - let tagged_value = TaggedValue::from_type(&self.input_type(&InputConnector::node(*node_id, input_index), network_path)); + let tagged_value = TaggedValue::from_type(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0); *input = NodeInput::value(tagged_value, true); } } @@ -453,114 +454,147 @@ impl NodeNetworkInterface { } } - /// Get the [`Type`] for any InputConnector - pub fn input_type(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Type { - // TODO: If the input_connector is a NodeInput::Value, return the type of the tagged value - let node_type_from_compiled_network = if let Some(node_id) = input_connector.node_id() { - let Some(current_network) = self.network(network_path) else { - log::error!("Could not get current network in input_type"); - return concrete!(()); - }; - let Some(node) = current_network.nodes.get(&node_id) else { - log::error!("Could not get node {node_id} in input_type"); - return concrete!(()); - }; - let node_id_path = [network_path, &[node_id]].concat().clone(); - match &node.implementation { - DocumentNodeImplementation::Network(nested_network) => { - let downstream_connection = nested_network - .nodes - .iter() - .flat_map(|(node_id, node)| node.inputs.iter().enumerate().map(|(input_index, input)| (InputConnector::node(*node_id, input_index), input))) - .chain(nested_network.exports.iter().enumerate().map(|(export_index, export)| (InputConnector::Export(export_index), export))) - .find(|(_, input)| { - if let NodeInput::Network { import_index, .. } = input { - *import_index == input_connector.input_index() - } else { - false - } - }); - if let Some((input_connector, _)) = downstream_connection { - Some(self.input_type(&input_connector, &node_id_path)) - } - // Nothing is connected to the import - else { - Some(concrete!(())) - } - } - DocumentNodeImplementation::ProtoNode(_) => { - // If a node has manual composition, then offset the input index by 1 since the proto node also includes the type of the parameter passed through manual composition. - let manual_composition_offset = if node.manual_composition.is_some() { 1 } else { 0 }; - self.resolved_types - .types - .get(node_id_path.as_slice()) - .map(|node_types| node_types.inputs[input_connector.input_index() + manual_composition_offset].clone()) - } - DocumentNodeImplementation::Extract => Some(concrete!(())), - } - } else if let Some(encapsulating_node_id) = network_path.last() { - let mut encapsulating_node_id_path = network_path.to_vec(); - encapsulating_node_id_path.pop(); - let output_types: Vec> = self.output_types(encapsulating_node_id, &encapsulating_node_id_path); - output_types.get(input_connector.input_index()).map_or_else( - || { + /// 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)?; + resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) + } + + /// 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 an ArtboardGroup. + return Some((concrete!(graphene_core::ArtboardGroup), TypeSource::OuterMostExportDefault)); + }; + + let output_type = self.output_types(encapsulating_node_id, encapsulating_node_id_path).into_iter().nth(export_index).flatten(); + if output_type.is_none() { warn!("Could not find output type for export node"); - Some(concrete!(())) - }, - |output_type| output_type.clone().map_or(Some(concrete!(())), Some), - ) - } else { - Some(concrete!(graphene_core::ArtboardGroup)) + } + return output_type; + } + }; + let Some(current_network) = self.network(network_path) else { + log::error!("Could not get current network in input_type"); + return None; + }; + let Some(node) = current_network.nodes.get(&node_id) 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().clone(); + 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(network_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(_) => { + // If a node has manual composition, then offset the input index by 1 since the proto node also includes the type of the parameter passed through manual composition. + let manual_composition_offset = if node.manual_composition.is_some() { 1 } else { 0 }; + self.resolved_types + .types + .get(node_id_path.as_slice()) + .and_then(|node_types| node_types.inputs.get(input_index + manual_composition_offset).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(network) = self.network(network_path) else { + log::error!("Could not get network in input_type"); + return (concrete!(()), TypeSource::Error("could not get network")); }; - node_type_from_compiled_network.unwrap_or_else(|| { - // TODO: Once there is type inference (#1621), replace this workaround approach when disconnecting node inputs with NodeInput::Node(ToDefaultNode), - // TODO: which would be a new node that implements the Default trait (i.e. `Default::default()`) - let Some(network) = self.network(network_path) else { - log::error!("Could not get network in input_type"); - return concrete!(()); - }; - // Resolve types from proto nodes in node_registry - let Some(node_id) = input_connector.node_id() else { - return concrete!(()); - }; - let Some(node) = network.nodes.get(&node_id) else { - return concrete!(()); - }; + let Some(node) = network.nodes.get(&node_id) else { + return (concrete!(()), TypeSource::Error("node id not in network")); + }; - fn type_from_node(node: &DocumentNode, input_index: usize) -> Type { - match &node.implementation { - DocumentNodeImplementation::ProtoNode(protonode) => { - let Some(node_types) = proto_node_type(protonode) else { return concrete!(()) }; + 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 = if node.manual_composition.is_some() { 1 } else { 0 }; + let skip_footprint = if node.manual_composition.is_some() { 1 } else { 0 }; - let Some(input_type) = std::iter::once(node_types.input.clone()).chain(node_types.parameters.clone()).nth(input_index + skip_footprint) else { - log::error!("Could not get type"); - return concrete!(()); - }; + let Some(input_type) = std::iter::once(node_types.input.clone()).chain(node_types.parameters.clone()).nth(input_index + skip_footprint) else { + log::error!("Could not get type"); + return (concrete!(()), TypeSource::Error("could not get the protonode's input")); + }; - input_type - } - DocumentNodeImplementation::Network(network) => { - for node in &network.nodes { - for (network_node_input_index, input) in node.1.inputs.iter().enumerate() { - if let NodeInput::Network { import_index, .. } = input { - if *import_index == input_index { - return type_from_node(node.1, network_node_input_index); - } - } - } - } - // Input is disconnected - concrete!(()) - } - _ => concrete!(()), - } + (input_type, TypeSource::RandomProtonodeImplementation) } + DocumentNodeImplementation::Network(_network) => { + // Attempt to resolve where this import is within the nested network + let outwards_wires = self.outward_wires(network_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(); - type_from_node(node, input_connector.input_index()) - }) + 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")); + }; + + // TODO: Once there is type inference (#1621), replace this workaround approach when disconnecting node inputs with NodeInput::Node(ToDefaultNode), + // TODO: which would be a new node that implements the Default trait (i.e. `Default::default()`) + self.guess_type_from_node(&mut network_path.to_vec(), node_id, input_connector.input_index()) } /// Retrieves the output types for a given document node and its exports. @@ -594,7 +628,7 @@ impl NodeNetworkInterface { /// /// 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_types(&self, node_id: &NodeId, network_path: &[NodeId]) -> Vec> { + pub fn output_types(&self, node_id: &NodeId, network_path: &[NodeId]) -> Vec> { let Some(network) = self.network(network_path) else { log::error!("Could not get network in output_types"); return Vec::new(); @@ -624,7 +658,7 @@ impl NodeNetworkInterface { output_types.push(nested_nodes_output_types.clone()); } NodeInput::Value { tagged_value, .. } => { - output_types.push(Some(tagged_value.ty())); + output_types.push(Some((tagged_value.ty(), TypeSource::TaggedValue))); } NodeInput::Network { .. } => { @@ -640,15 +674,15 @@ impl NodeNetworkInterface { } graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode) => { let node_id_path = &[network_path, &[*node_id]].concat(); - let primary_output_type = self.resolved_types.types.get(node_id_path).map(|ty| ty.output.clone()).or_else(|| { - let node_types = proto_node_type(protonode)?; - Some(node_types.output.clone()) + let primary_output_type = 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.output.clone(), TypeSource::RandomProtonodeImplementation)) }); output_types.push(primary_output_type); } graph_craft::document::DocumentNodeImplementation::Extract => { - output_types.push(Some(concrete!(()))); + output_types.push(Some((concrete!(()), TypeSource::Error("extract node")))); } } output_types @@ -692,7 +726,7 @@ impl NodeNetworkInterface { let mut encapsulating_path = network_path.to_vec(); let encapsulating_node_id = encapsulating_path.pop().unwrap(); - let input_type = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path); + let (input_type, type_source) = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path); let data_type = FrontendGraphDataType::with_type(&input_type); let import_name = if import_name.is_empty() { input_type.clone().nested_type().to_string() } else { import_name }; @@ -710,7 +744,7 @@ impl NodeNetworkInterface { FrontendGraphOutput { data_type, name: import_name, - resolved_type: Some(format!("{input_type:?}")), + resolved_type: Some(format!("{input_type:?} from {type_source:?}")), connected_to, }, click_target, @@ -742,13 +776,13 @@ impl NodeNetworkInterface { let (frontend_data_type, input_type) = if let NodeInput::Node { node_id, output_index, .. } = export { let output_types = self.output_types(node_id, network_path); - if let Some(output_type) = output_types.get(*output_index).cloned().flatten() { - (FrontendGraphDataType::with_type(&output_type), Some(output_type.clone())) + if let Some((output_type, type_source)) = output_types.get(*output_index).cloned().flatten() { + (FrontendGraphDataType::with_type(&output_type), Some((output_type, type_source))) } else { (FrontendGraphDataType::General, None) } } else if let NodeInput::Value { tagged_value, .. } = export { - (FrontendGraphDataType::with_type(&tagged_value.ty()), Some(tagged_value.ty())) + (FrontendGraphDataType::with_type(&tagged_value.ty()), Some((tagged_value.ty(), TypeSource::TaggedValue))) // TODO: Get type from parent node input when is possible // else if let NodeInput::Network { import_type, .. } = export { // (FrontendGraphDataType::with_type(import_type), Some(import_type.clone())) @@ -783,7 +817,7 @@ impl NodeNetworkInterface { } else { input_type .clone() - .map(|input_type| input_type.nested_type().to_string()) + .map(|(input_type, _)| input_type.nested_type().to_string()) .unwrap_or(format!("Export {}", export_index + 1)) }; @@ -791,7 +825,7 @@ impl NodeNetworkInterface { FrontendGraphInput { data_type: frontend_data_type, name: export_name, - resolved_type: input_type.map(|input| format!("{input:?}")), + resolved_type: input_type.map(|(export_type, source)| format!("{export_type:?} from {source:?}")), connected_to, }, click_target, @@ -1407,7 +1441,8 @@ impl NodeNetworkInterface { } } -fn proto_node_type(protonode: &graph_craft::ProtoNodeIdentifier) -> Option<&graphene_std::NodeIOTypes> { +/// 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 Some(node_io_hashmap) = NODE_REGISTRY.get(protonode) else { log::error!("Could not get hashmap for proto node: {protonode:?}"); return None; @@ -3325,7 +3360,7 @@ impl NodeNetworkInterface { } } - let tagged_value = TaggedValue::from_type(&self.input_type(input_connector, network_path)); + let tagged_value = TaggedValue::from_type(&self.input_type(input_connector, network_path).0); let value_input = NodeInput::value(tagged_value, true); @@ -4838,8 +4873,26 @@ impl<'a> Iterator for FlowIter<'a> { } } +/// Represents the source of a resolved type (for debugging) +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum TypeSource { + Compiled, + RandomProtonodeImplementation, + DocumentNodeDefault, + TaggedValue, + OuterMostExportDefault, + + Error(&'static str), +} + +impl Default for TypeSource { + fn default() -> Self { + Self::Error("no source") + } +} + /// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum InputConnector { #[serde(rename = "node")] Node { @@ -4879,7 +4932,7 @@ impl InputConnector { } /// Represents an output connector -#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum OutputConnector { #[serde(rename = "node")] Node { @@ -4916,6 +4969,14 @@ impl OutputConnector { _ => None, } } + + pub fn from_input(input: &NodeInput) -> Option { + match input { + NodeInput::Network { import_index, .. } => Some(Self::Import(*import_index)), + NodeInput::Node { node_id, output_index, .. } => Some(Self::node(*node_id, *output_index)), + _ => None, + } + } } #[derive(Debug, Clone)] diff --git a/node-graph/graph-craft/benches/compile_demo_art.rs b/node-graph/graph-craft/benches/compile_demo_art.rs index 49ebb7dd..c51e49d5 100644 --- a/node-graph/graph-craft/benches/compile_demo_art.rs +++ b/node-graph/graph-craft/benches/compile_demo_art.rs @@ -1,6 +1,5 @@ -use graph_craft::document::NodeNetwork; -use graph_craft::graphene_compiler::Compiler; -use graph_craft::proto::ProtoNetwork; +#[cfg(any(feature = "criterion", feature = "iai"))] +use graph_craft::{document::NodeNetwork, graphene_compiler::Compiler, proto::ProtoNetwork}; #[cfg(feature = "criterion")] use criterion::{black_box, criterion_group, criterion_main, Criterion}; @@ -8,11 +7,13 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; #[cfg(all(not(feature = "criterion"), feature = "iai"))] use iai_callgrind::{black_box, library_benchmark, library_benchmark_group, main}; +#[cfg(any(feature = "criterion", feature = "iai"))] fn load_network(document_string: &str) -> NodeNetwork { let document: serde_json::Value = serde_json::from_str(document_string).expect("Failed to parse document"); serde_json::from_value::(document["network_interface"]["network"].clone()).expect("Failed to parse document") } +#[cfg(any(feature = "criterion", feature = "iai"))] fn compile(network: NodeNetwork) -> ProtoNetwork { let compiler = Compiler {}; compiler.compile_single(network).unwrap() @@ -41,6 +42,7 @@ fn compile_to_proto(c: &mut Criterion) { #[cfg_attr(all(feature = "iai", not(feature = "criterion")), library_benchmark)] #[cfg_attr(all(feature = "iai", not(feature="criterion")), benches::with_setup(args = ["isometric-fountain", "painted-dreams", "procedural-string-lights", "red-dress", "valley-of-spires"], setup = load_from_name))] +#[cfg(all(not(feature = "criterion"), feature = "iai"))] pub fn iai_compile_to_proto(input: NodeNetwork) { black_box(compile(input)); } @@ -56,3 +58,6 @@ library_benchmark_group!(name = compile_group; benchmarks = iai_compile_to_proto #[cfg(all(not(feature = "criterion"), feature = "iai"))] main!(library_benchmark_groups = compile_group); + +#[cfg(all(not(feature = "criterion"), not(feature = "iai")))] +fn main() {}