From 0766a4b29d29e49b3a30b8fc964ded65d7ef81d7 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 29 Aug 2025 19:43:41 -0700 Subject: [PATCH 1/6] Refactor TypeSource --- .../document/document_message_handler.rs | 15 +- .../document/graph_operation/utility_types.rs | 4 +- .../node_graph/node_graph_message_handler.rs | 51 +-- .../document/node_graph/node_properties.rs | 27 +- .../document/node_graph/utility_types.rs | 8 - .../utility_types/network_interface.rs | 383 ++---------------- .../network_interface/resolved_types.rs | 305 ++++++++++++++ .../graph_modification_utils.rs | 4 +- .../common_functionality/utility_functions.rs | 4 +- node-graph/graph-craft/src/document.rs | 8 + .../src/dynamic_executor.rs | 5 - node-graph/interpreted-executor/src/util.rs | 1 + 12 files changed, 390 insertions(+), 425 deletions(-) create mode 100644 editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index e86f79b282..85fb01c76a 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; @@ -2819,16 +2819,11 @@ impl DocumentMessageHandler { .tooltip("Add an operation to the end of this layer's chain of nodes") .disabled(!has_selection || has_multiple_selection) .popover_layout({ - // Showing only compatible types + // Showing only compatible types for the layer based on the output type of the node upstream from its horizontal input 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 80e005448f..8ecc086249 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), &[]); + if layer_input_type.compiled_nested_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 c0ad7597fb..c0243e673a 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 @@ -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}; @@ -881,7 +881,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 = network_interface.input_type(clicked_input, breadcrumb_network_path).displayed_type(); return; } @@ -891,8 +891,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 = output_type.displayed_type(); self.update_node_graph_hints(responses); return; @@ -1211,21 +1211,17 @@ impl<'a> MessageHandler> for NodeG } // 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 > viewport.size().y() - 173. { -173. } else { 0. }; let appear_above_mouse = if ipp.mouse.position.y > viewport.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; + let compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string(); + self.context_menu = Some(ContextMenuInformation { context_menu_coordinates: ((point.x + node_graph_shift.x) as i32, (point.y + node_graph_shift.y) as i32), context_menu_data: ContextMenuData::CreateNode { compatible_type }, @@ -1996,12 +1992,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 => { @@ -2115,16 +2106,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, }; @@ -2437,17 +2419,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()); 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 20bc85a508..df9a20089c 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; @@ -85,7 +85,7 @@ pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec().for_socket(parameter_info).property_row(); let document_node = match get_document_node(node_id, context) { @@ -1185,7 +1185,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp use graphene_std::raster::selective_color::*; let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context); - default_info.exposeable = false; + default_info.exposable = false; let colors = enum_choice::().for_socket(default_info).property_row(); let document_node = match get_document_node(node_id, context) { @@ -1574,6 +1574,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper let mut display_decimal_places = None; let mut step = None; let mut unit_suffix = None; + let input_type = match implementation { DocumentNodeImplementation::ProtoNode(proto_node_identifier) => 'early_return: { if let Some(field) = graphene_std::registry::NODE_METADATA @@ -1610,7 +1611,12 @@ 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) + .compiled_nested_type() + .cloned() + .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) @@ -1989,13 +1995,16 @@ pub struct ParameterWidgetsInfo<'a> { description: String, input_type: FrontendGraphDataType, blank_assist: bool, - exposeable: bool, + exposable: bool, } 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 = context + .network_interface + .input_type(&InputConnector::node(node_id, index), context.selection_network_path) + .displayed_type(); let document_node = context.network_interface.document_node(&node_id, context.selection_network_path); ParameterWidgetsInfo { @@ -2006,7 +2015,7 @@ impl<'a> ParameterWidgetsInfo<'a> { description, input_type, blank_assist, - exposeable: true, + exposable: true, } } } 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 2d03c9824f..8cbe3cacd6 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -1,4 +1,3 @@ -use crate::messages::portfolio::document::utility_types::network_interface::TypeSource; use glam::IVec2; use graph_craft::document::NodeId; use graph_craft::document::value::TaggedValue; @@ -41,13 +40,6 @@ impl FrontendGraphDataType { _ => Self::General, } } - - pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self { - match type_source { - TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General, - _ => Self::from_type(input), - } - } } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] 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 85fb237cd8..8f4079110f 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1,5 +1,6 @@ mod deserialization; mod memo_network; +mod resolved_types; use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier, NodeRelations}; use super::misc::PTZ; @@ -7,30 +8,27 @@ 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, FrontendGraphInput, FrontendGraphOutput}; +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::ContextDependencies; 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 memo_network::MemoNetwork; 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; /// 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. @@ -459,12 +457,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::Import { .. } = 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); } } @@ -472,8 +470,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()?) } @@ -494,278 +492,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::warn!("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::Import { .. } => { - // 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) @@ -897,8 +623,10 @@ impl NodeNetworkInterface { 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 input_type = self.input_type(input_connector, network_path); + let data_type = input_type.displayed_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 { @@ -928,8 +656,8 @@ impl NodeNetworkInterface { let export_name = if !export_name.is_empty() { export_name - } else if *export_type.nested_type() != concrete!(()) { - export_type.nested_type().to_string() + } else if let Some(export_type_name) = input_type.compiled_nested_type_name() { + export_type_name } else { format!("Export index {}", export_index) }; @@ -937,11 +665,12 @@ impl NodeNetworkInterface { (export_name, String::new()) } }; + Some(FrontendGraphInput { data_type, + resolved_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, }) @@ -949,7 +678,8 @@ impl NodeNetworkInterface { /// Returns None if there is an error, it is the document network, a hidden primary output or import pub fn frontend_output_from_connector(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option { - let (output_type, type_source) = self.output_type(output_connector, network_path); + 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 @@ -960,14 +690,7 @@ impl NodeNetworkInterface { 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) - }; - + let output_name = if !output_name.is_empty() { output_name } else { output_type.resolved_type_name() }; (output_name, String::new()) } OutputConnector::Import(import_index) => { @@ -982,17 +705,19 @@ impl NodeNetworkInterface { }; 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!(()) { + 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) + format!("Import index {}", import_index) }; + (import_name, description) } }; - - let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source); - + let data_type = output_type.displayed_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)) @@ -1017,8 +742,8 @@ impl NodeNetworkInterface { Some(FrontendGraphOutput { data_type, + resolved_type, name, - resolved_type: format!("{:?}", output_type), description, connected_to, }) @@ -1252,7 +977,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() }; @@ -1288,9 +1013,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() } @@ -1717,30 +1442,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> { @@ -2716,7 +2417,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 = self.input_type(&input, network_path).displayed_type(); let wire_path_update = Some(WirePath { path_string, data_type, @@ -2754,7 +2455,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 = self.input_type(input, network_path).displayed_type(); Some(WirePath { path_string, data_type, @@ -4409,7 +4110,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); @@ -6024,22 +5725,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/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs new file mode 100644 index 0000000000..695a435c6d --- /dev/null +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -0,0 +1,305 @@ +use std::collections::{HashMap, HashSet}; + +use graph_craft::document::value::TaggedValue; +use graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput}; +use graph_craft::{Type, concrete}; +use graphene_std::uuid::NodeId; +use interpreted_executor::dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta}; +use interpreted_executor::node_registry::NODE_REGISTRY; + +use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType; +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 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(), + } + } + + /// The reduced set of frontend types for displaying color. + pub fn displayed_type(&self) -> FrontendGraphDataType { + match self.compiled_nested_type() { + Some(nested_type) => FrontendGraphDataType::from_type(nested_type), + None => FrontendGraphDataType::General, + } + } +} + +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(value) = definition.node_template.document_node.inputs.get(*input_index).and_then(|input| input.as_value()) { + return TypeSource::DocumentNodeDefinition(value.ty()); + } + } + } + } + input_type + } + + NodeInput::Value { tagged_value, .. } => TypeSource::TaggedValue(tagged_value.ty()), + NodeInput::Import { 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 = self.valid_input_types(input_connector, network_path); + + match valid_types.pop() { + Some(valid_type) => valid_type, + None => { + match self.random_type_for_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]) -> 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).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); + // 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() == input_type.compiled_nested_type() + || node_io.inputs.get(iterator_index) == input_type.compiled_nested_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() + } + } + } + + 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() + .map(|input_connector| self.valid_input_types(input_connector, &network_path).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::>()) + } + + /// Performs a downstream iteration from an input connector to the next protonode and selects a random type from its implementations. + pub fn random_type_for_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_type_for_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_type_for_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 0c2d87a1f0..c0d1081501 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -484,8 +484,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), &[]); - layer_input_type == concrete!(Table>) || layer_input_type == concrete!(Table>) + layer_input_type.compiled_nested_type() == Some(&concrete!(Table>)) || layer_input_type.compiled_nested_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 b19b3dac4f..40be20cce7 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.compiled_nested_type().as_deref() != Some(&concrete!(Table)) { return None; } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 583335cd2d..4f84913fd4 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -211,6 +211,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..e6004f5e4c 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -39,11 +39,6 @@ pub struct NodeTypes { 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)] diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index 0ae85d5c97..9eab6f2bc5 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -61,6 +61,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc Date: Sat, 15 Nov 2025 03:11:46 -0800 Subject: [PATCH 2/6] Add complete valid types --- .../utility_types/network_interface.rs | 2 +- .../network_interface/resolved_types.rs | 114 +++++++++++++++--- node-graph/libraries/core-types/src/types.rs | 16 ++- 3 files changed, 111 insertions(+), 21 deletions(-) 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 8f4079110f..b690a106ac 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -671,7 +671,7 @@ impl NodeNetworkInterface { resolved_type, name, description, - valid_types: self.valid_input_types(input_connector, network_path).iter().map(|ty| ty.to_string()).collect(), + valid_types: self.potential_valid_input_types(input_connector, network_path).iter().map(|ty| ty.to_string()).collect(), connected_to, }) } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index 695a435c6d..ca58c5e654 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput}; -use graph_craft::{Type, concrete}; +use graph_craft::{ProtoNodeIdentifier, Type, concrete}; use graphene_std::uuid::NodeId; use interpreted_executor::dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta}; use interpreted_executor::node_registry::NODE_REGISTRY; @@ -43,6 +43,32 @@ pub enum TypeSource { } impl TypeSource { + /// The reduced set of frontend types for displaying color. + pub fn displayed_type(&self) -> FrontendGraphDataType { + match self.compiled_nested_type() { + Some(nested_type) => match TaggedValue::from_type_or_none(nested_type) { + TaggedValue::U32(_) + | TaggedValue::U64(_) + | TaggedValue::F32(_) + | TaggedValue::F64(_) + | TaggedValue::DVec2(_) + | TaggedValue::F64Array4(_) + | TaggedValue::VecF64(_) + | TaggedValue::VecDVec2(_) + | TaggedValue::DAffine2(_) => FrontendGraphDataType::Number, + TaggedValue::Artboard(_) => FrontendGraphDataType::Artboard, + TaggedValue::Graphic(_) => FrontendGraphDataType::Graphic, + TaggedValue::Raster(_) => FrontendGraphDataType::Raster, + TaggedValue::Vector(_) => FrontendGraphDataType::Vector, + TaggedValue::Color(_) => FrontendGraphDataType::Color, + TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => FrontendGraphDataType::Gradient, + TaggedValue::String(_) => FrontendGraphDataType::Typography, + _ => FrontendGraphDataType::General, + }, + None => FrontendGraphDataType::General, + } + } + pub fn compiled_nested_type(&self) -> Option<&Type> { match self { TypeSource::Compiled(compiled_type) => Some(compiled_type.nested_type()), @@ -71,14 +97,6 @@ impl TypeSource { TypeSource::Error(_) => "Error".to_string(), } } - - /// The reduced set of frontend types for displaying color. - pub fn displayed_type(&self) -> FrontendGraphDataType { - match self.compiled_nested_type() { - Some(nested_type) => FrontendGraphDataType::from_type(nested_type), - None => FrontendGraphDataType::General, - } - } } impl NodeNetworkInterface { @@ -126,7 +144,7 @@ impl NodeNetworkInterface { TypeSource::TaggedValue(value) => value, TypeSource::DocumentNodeDefinition(definition) => definition, TypeSource::Unknown => { - let mut valid_types = self.valid_input_types(input_connector, network_path); + let mut valid_types = self.potential_valid_input_types(input_connector, network_path); match valid_types.pop() { Some(valid_type) => valid_type, @@ -147,7 +165,8 @@ impl NodeNetworkInterface { TaggedValue::from_type_or_none(&guaranteed_type) } - pub fn valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { + /// A list of all valid input types for this specific node. + pub fn potential_valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { 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)]; @@ -171,7 +190,7 @@ impl NodeNetworkInterface { let intersection: HashSet = inputs_from_import .clone() .iter() - .map(|input_connector| self.valid_input_types(input_connector, &nested_path).into_iter().collect::>()) + .map(|input_connector| self.potential_valid_input_types(input_connector, &nested_path).into_iter().collect::>()) .fold(None, |acc: Option>, set| match acc { Some(acc_set) => Some(acc_set.intersection(&set).cloned().collect()), None => Some(set), @@ -191,10 +210,9 @@ impl NodeNetworkInterface { .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); - // 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() == input_type.compiled_nested_type() - || node_io.inputs.get(iterator_index) == input_type.compiled_nested_type() + // TODO: Fix type checking for different call arguments + // For example a node input of (Footprint) -> Vector would not be compatible with a node that is called with () and returns Vector + node_io.inputs.get(iterator_index).map(|ty| ty.nested_type()) == input_type.compiled_nested_type() }); if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } }) @@ -207,6 +225,66 @@ impl NodeNetworkInterface { } } + /// Performs a downstream traversal to ensure input type will work in the full context of the graph. + pub fn complete_valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> 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.compiled_nested_type() { + Some(input_type) => node_io.inputs.get(iterator_index).is_some_and(|node_io_input_type| node_io_input_type.nested_type() == 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()) + } + } + } + } + } + pub fn output_type(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> TypeSource { match output_connector { OutputConnector::Node { node_id, output_index } => { @@ -243,7 +321,7 @@ impl NodeNetworkInterface { let intersection = inputs_from_import .clone() .iter() - .map(|input_connector| self.valid_input_types(input_connector, &network_path).into_iter().collect::>()) + .map(|input_connector| self.potential_valid_input_types(input_connector, &network_path).into_iter().collect::>()) .fold(None, |acc: Option>, set| match acc { Some(acc_set) => Some(acc_set.intersection(&set).cloned().collect()), None => Some(set), @@ -281,7 +359,7 @@ impl NodeNetworkInterface { 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() + implementations.keys().min().and_then(|node_io| node_io.inputs.get(input_connector.input_index())).cloned() } DocumentNodeImplementation::Extract => None, } diff --git a/node-graph/libraries/core-types/src/types.rs b/node-graph/libraries/core-types/src/types.rs index 64c742618a..14a0fae8e2 100644 --- a/node-graph/libraries/core-types/src/types.rs +++ b/node-graph/libraries/core-types/src/types.rs @@ -77,7 +77,7 @@ macro_rules! fn_type_fut { }; } -#[derive(Clone, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] pub struct NodeIOTypes { pub call_argument: Type, pub return_value: Type, @@ -229,8 +229,20 @@ impl PartialEq for TypeDescriptor { } } +impl Ord for TypeDescriptor { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.name.cmp(&other.name) + } +} + +impl PartialOrd for TypeDescriptor { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + /// Graph runtime type information used for type inference. -#[derive(Clone, PartialEq, Eq, Hash, specta::Type, serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, specta::Type, serde::Serialize, serde::Deserialize)] pub enum Type { /// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete type. Generic(Cow<'static, str>), From 31eba8465fcafcf09a8bb32cb92568a37dabb657 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 17 Nov 2025 01:31:59 -0800 Subject: [PATCH 3/6] Add invalid type --- .../graph_operation_message_handler.rs | 8 + .../document_node_derive.rs | 30 +++ .../node_graph/node_graph_message_handler.rs | 14 +- .../document/node_graph/node_properties.rs | 1 - .../document/node_graph/utility_types.rs | 1 + .../utility_types/network_interface.rs | 22 ++- .../network_interface/resolved_types.rs | 187 ++++++++---------- frontend/src/components/Editor.svelte | 2 + frontend/src/messages.ts | 2 +- node-graph/interpreted-executor/src/util.rs | 2 +- node-graph/libraries/core-types/src/types.rs | 16 +- 11 files changed, 146 insertions(+), 139 deletions(-) diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index f40b07d1c9..d22eeda8b1 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -7,10 +7,12 @@ use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; use glam::{DAffine2, DVec2, IVec2}; +use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graphene_std::Color; use graphene_std::renderer::Quad; use graphene_std::renderer::convert_usvg_path::convert_usvg_path; +use graphene_std::table::Table; use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; @@ -140,10 +142,16 @@ impl MessageHandler> for skip_rerender: true, }); } + // Set the bottom input of the artboard back to artboard + let bottom_input = NodeInput::value(TaggedValue::Artboard(Table::new()), true); + network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]); } else { // We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input. network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]); network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 1), primary_input, &[]); + // Set the bottom input of the artboard back to artboard + let bottom_input = NodeInput::value(TaggedValue::Artboard(Table::new()), true); + network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]); } } responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] }); diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index a0e09e1296..84e52e7022 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -6,7 +6,37 @@ use graphene_std::registry::*; use graphene_std::*; use std::collections::HashSet; +/// Traverses a document node template and metadata in parallel to link the protonodes to their reference +fn traverse_node(node: &DocumentNode, node_metadata: &mut DocumentNodePersistentMetadata) { + match &node.implementation { + DocumentNodeImplementation::Network(node_network) => { + for (nested_node_id, nested_node) in node_network.nodes.iter() { + let nested_metadata = node_metadata + .network_metadata + .as_mut() + .expect("Network node must have network metadata") + .persistent_metadata + .node_metadata + .get_mut(nested_node_id) + .expect("Network metadata must have corresponding node id"); + traverse_node(nested_node, &mut nested_metadata.persistent_metadata); + } + } + DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { + if let Some(metadata) = NODE_METADATA.lock().unwrap().get(&proto_node_identifier) { + node_metadata.reference = Some(metadata.display_name.to_string()); + } + } + DocumentNodeImplementation::Extract => {} + } +} + pub(super) fn post_process_nodes(mut custom: Vec) -> Vec { + // Link the protonodes with custom networks to their reference + for node in custom.iter_mut() { + traverse_node(&node.node_template.document_node, &mut node.node_template.persistent_node_metadata); + } + // Remove struct generics for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() { let NodeTemplate { 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 c0243e673a..ace1da3dd9 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 @@ -23,7 +23,6 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use crate::messages::viewport::Position; use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; -use graph_craft::proto::GraphErrors; use graphene_std::math::math_ext::QuadExt; use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath; use graphene_std::*; @@ -51,7 +50,6 @@ pub struct NodeGraphMessageContext<'a> { pub struct NodeGraphMessageHandler { // TODO: Remove network and move to NodeNetworkInterface pub network: Vec, - pub node_graph_errors: GraphErrors, has_selection: bool, widgets: [LayoutGroup; 2], /// Used to add a transaction for the first node move when dragging. @@ -1628,7 +1626,7 @@ impl<'a> MessageHandler> for NodeG if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y { nodes.push(*node_id); } - for error in &self.node_graph_errors { + for error in &network_interface.resolved_types.node_graph_errors { if error.node_path.contains(node_id) { nodes.push(*node_id); } @@ -1992,8 +1990,7 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SendGraph); } NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => { - network_interface.resolved_types.update(resolved_types); - self.node_graph_errors = node_graph_errors; + network_interface.resolved_types.update(resolved_types, node_graph_errors); } NodeGraphMessage::UpdateActionButtons => { if selection_network_path == breadcrumb_network_path { @@ -2554,13 +2551,14 @@ impl NodeGraphMessageHandler { let locked = network_interface.is_locked(&node_id, breadcrumb_network_path); - let errors = self + let errors = network_interface + .resolved_types .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)) { + if network_interface.resolved_types.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 @@ -2743,7 +2741,6 @@ impl Default for NodeGraphMessageHandler { fn default() -> Self { Self { network: Vec::new(), - node_graph_errors: Vec::new(), has_selection: false, widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: Vec::new() }], drag_start: None, @@ -2775,7 +2772,6 @@ impl Default for NodeGraphMessageHandler { impl PartialEq for NodeGraphMessageHandler { fn eq(&self, other: &Self) -> bool { self.network == other.network - && self.node_graph_errors == other.node_graph_errors && self.has_selection == other.has_selection && self.widgets == other.widgets && self.drag_start == other.drag_start 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 df9a20089c..349b9d9365 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1574,7 +1574,6 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper let mut display_decimal_places = None; let mut step = None; let mut unit_suffix = None; - let input_type = match implementation { DocumentNodeImplementation::ProtoNode(proto_node_identifier) => 'early_return: { if let Some(field) = graphene_std::registry::NODE_METADATA 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 8cbe3cacd6..79918d2220 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -16,6 +16,7 @@ pub enum FrontendGraphDataType { Color, Gradient, Typography, + Invalid, } impl FrontendGraphDataType { 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 b690a106ac..4f96405859 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -8,7 +8,7 @@ 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, FrontendGraphInput, FrontendGraphOutput}; +use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; 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; @@ -625,7 +625,7 @@ impl NodeNetworkInterface { } let input_type = self.input_type(input_connector, network_path); let data_type = input_type.displayed_type(); - let resolved_type = input_type.resolved_type_name(); + let resolved_type = input_type.resolved_type_node_string(); let connected_to = self .upstream_output_connector(input_connector, network_path) @@ -656,7 +656,7 @@ impl NodeNetworkInterface { let export_name = if !export_name.is_empty() { export_name - } else if let Some(export_type_name) = input_type.compiled_nested_type_name() { + } else if let Some(export_type_name) = input_type.compiled_nested_type().map(|nested| nested.to_string()) { export_type_name } else { format!("Export index {}", export_index) @@ -679,7 +679,6 @@ impl NodeNetworkInterface { /// Returns None if there is an error, it is the document network, a hidden primary output or import pub fn frontend_output_from_connector(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option { 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 @@ -690,7 +689,7 @@ impl NodeNetworkInterface { 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() }; + let output_name = if !output_name.is_empty() { output_name } else { output_type.resolved_type_node_string() }; (output_name, String::new()) } OutputConnector::Import(import_index) => { @@ -707,7 +706,7 @@ impl NodeNetworkInterface { let import_name = if !import_name.is_empty() { import_name - } else if let Some(import_type_name) = output_type.compiled_nested_type_name() { + } else if let Some(import_type_name) = output_type.compiled_nested_type().map(|nested| nested.to_string()) { import_type_name } else { format!("Import index {}", import_index) @@ -717,7 +716,7 @@ impl NodeNetworkInterface { } }; let data_type = output_type.displayed_type(); - let resolved_type = output_type.resolved_type_name(); + let resolved_type = output_type.resolved_type_node_string(); let mut connected_to = self .outward_wires(network_path) .and_then(|outward_wires| outward_wires.get(output_connector)) @@ -977,7 +976,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).resolved_type_name() + self.input_type(&InputConnector::node(*node_id, input_index), network_path).resolved_type_node_string() } else { input_metadata.input_name.to_string() }; @@ -1015,7 +1014,7 @@ impl NodeNetworkInterface { pub fn description(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { self.get_node_definition(node_id, network_path) .map(|node_definition| node_definition.description.to_string()) - .filter(|description: &String| description != "TODO") + .filter(|description| description != "TODO") .unwrap_or_default() } @@ -2455,7 +2454,10 @@ 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 = self.input_type(input, network_path).displayed_type(); + let data_type = self + .upstream_output_connector(input, network_path) + .map(|output| self.output_type(&output, network_path).displayed_type()) + .unwrap_or(FrontendGraphDataType::General); Some(WirePath { path_string, data_type, diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index ca58c5e654..6163592456 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -2,7 +2,8 @@ use std::collections::{HashMap, HashSet}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput}; -use graph_craft::{ProtoNodeIdentifier, Type, concrete}; +use graph_craft::proto::GraphErrors; +use graph_craft::{Type, concrete}; use graphene_std::uuid::NodeId; use interpreted_executor::dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta}; use interpreted_executor::node_registry::NODE_REGISTRY; @@ -14,37 +15,42 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp #[derive(Debug, Default)] pub struct ResolvedDocumentNodeTypes { pub types: HashMap, NodeTypes>, + pub node_graph_errors: GraphErrors, } impl ResolvedDocumentNodeTypes { - pub fn update(&mut self, delta: ResolvedDocumentNodeTypesDelta) { + pub fn update(&mut self, delta: ResolvedDocumentNodeTypesDelta, errors: GraphErrors) { 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()); } + self.node_graph_errors = errors; } } /// 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 + /// A type that has been compiled based on all upstream types. Compiled(Type), - // The type of value inputs + /// 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 + /// When the input/output is not compiled. The Type is from the document node definition, or () if it doesn't exist. Unknown, - + /// When there is a node graph error for the inputs to a node. The Type is from the document node definition, or () if it doesn't exist. + Invalid, + /// When there is an error in the algorithm for determining the input/output type (indicates a bug in the editor). Error(&'static str), } impl TypeSource { /// The reduced set of frontend types for displaying color. pub fn displayed_type(&self) -> FrontendGraphDataType { + if matches!(self, TypeSource::Invalid) { + return FrontendGraphDataType::Invalid; + }; match self.compiled_nested_type() { Some(nested_type) => match TaggedValue::from_type_or_none(nested_type) { TaggedValue::U32(_) @@ -77,29 +83,60 @@ impl TypeSource { } } - /// 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 { + pub fn resolved_type_tooltip_string(&self) -> String { + match self { + TypeSource::Compiled(compiled_type) => format!("Data Type: {:?}", compiled_type.nested_type().to_string()), + TypeSource::TaggedValue(value_type) => format!("Data Type: {:?}", value_type.nested_type().to_string()), + TypeSource::Unknown => "Unknown Data Type".to_string(), + TypeSource::Invalid => "Invalid Type Combination".to_string(), + TypeSource::Error(_) => "Error Getting Data Type".to_string(), + } + } + + /// The type to display in the node row. + pub fn resolved_type_node_string(&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::Invalid => "Invalid".to_string(), TypeSource::Error(_) => "Error".to_string(), } } } impl NodeNetworkInterface { + fn input_has_error(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> bool { + match input_connector { + InputConnector::Node { node_id, input_index } => { + let Some(implementation) = self.implementation(node_id, network_path) else { + log::error!("Could not get implementation in input_has_error"); + return false; + }; + let node_path = [network_path, &[*node_id]].concat(); + match implementation { + DocumentNodeImplementation::Network(_) => { + let Some(map) = self.outward_wires(&node_path) else { return false }; + let Some(outward_wires) = map.get(&OutputConnector::Import(*input_index)) else { return false }; + outward_wires.clone().iter().any(|connector| match connector { + InputConnector::Node { node_id, input_index } => self.input_has_error(&InputConnector::node(*node_id, *input_index), &node_path), + InputConnector::Export(_) => false, + }) + } + DocumentNodeImplementation::ProtoNode(_) => self.resolved_types.node_graph_errors.iter().any(|error| error.node_path == node_path), + DocumentNodeImplementation::Extract => false, + } + } + InputConnector::Export(_) => false, + } + } + /// 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 { @@ -109,25 +146,19 @@ impl NodeNetworkInterface { 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(value) = definition.node_template.document_node.inputs.get(*input_index).and_then(|input| input.as_value()) { - return TypeSource::DocumentNodeDefinition(value.ty()); - } - } - } + let output_connector = OutputConnector::node(*node_id, *output_index); + // First check if there is an error with this node or any protonodes it is connected to + if self.input_has_error(input_connector, network_path) { + return TypeSource::Invalid; } - input_type + self.output_type(&output_connector, network_path) } NodeInput::Value { tagged_value, .. } => TypeSource::TaggedValue(tagged_value.ty()), NodeInput::Import { 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"); + return TypeSource::Error("Could not get type of import in document network since it has no imports"); }; self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path) } @@ -142,20 +173,16 @@ impl NodeNetworkInterface { 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 = self.potential_valid_input_types(input_connector, network_path); - - match valid_types.pop() { - Some(valid_type) => valid_type, - None => { - match self.random_type_for_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::Unknown | TypeSource::Invalid => { + let mut ret = concrete!(()); + 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(value) = definition.node_template.document_node.inputs.get(*input_index).and_then(|input| input.as_value()) { + ret = value.ty(); } } } + ret } TypeSource::Error(e) => { log::error!("Error getting tagged_value_from_input for {input_connector:?} {e}"); @@ -172,18 +199,18 @@ impl NodeNetworkInterface { 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"); + log::error!("Could not get node implementation in potential_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"); + log::error!("Could not get outward wires in potential_valid_input_types"); return Vec::new(); }; let Some(inputs_from_import) = outward_wires.get(&OutputConnector::Import(*input_index)) else { - log::error!("Could not get inputs from import in valid_input_types"); + log::error!("Could not get inputs from import in potential_valid_input_types"); return Vec::new(); }; @@ -201,7 +228,7 @@ impl NodeNetworkInterface { } 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"); + log::error!("Protonode {proto_node_identifier:?} not found in registry in potential_valid_input_types"); return Vec::new(); }; let number_of_inputs = self.number_of_inputs(node_id, network_path); @@ -230,13 +257,13 @@ impl NodeNetworkInterface { 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)); + return Err(format!("Could not get node implementation for {:?} {} in complete_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")); + return Err(format!("Protonode {proto_node_identifier:?} not found in registry in complete_valid_input_types")); }; let valid_output_types = match self.valid_output_types(&OutputConnector::node(*node_id, 0), network_path) { Ok(valid_types) => valid_types, @@ -273,9 +300,8 @@ impl NodeNetworkInterface { 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 { + let render_node = graphene_std::render_node::render::IDENTIFIER; + let Some(implementations) = NODE_REGISTRY.get(&render_node) else { return Err(format!("Protonode {render_node:?} not found in registry")); }; Ok(implementations.iter().map(|(types, _)| types.inputs[1].clone()).collect()) @@ -303,19 +329,24 @@ impl NodeNetworkInterface { } 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"); + return TypeSource::Error("Cannot get import type in document network since it has no imports"); }; - self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path) + let mut input_type = self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path); + if matches!(input_type, TypeSource::Invalid) { + input_type = TypeSource::Unknown + } + return input_type; } } } - // 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> { + + /// The valid output types are all types that are valid for each downstream connection. + fn valid_output_types(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> 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()); + return Err("Could not get outward wires in valid_output_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()); + return Err("Could not get inputs from import in valid_output_types".to_string()); }; let intersection = inputs_from_import @@ -330,54 +361,4 @@ impl NodeNetworkInterface { Ok(intersection.into_iter().collect::>()) } - - /// Performs a downstream iteration from an input connector to the next protonode and selects a random type from its implementations. - pub fn random_type_for_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_type_for_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().min().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_type_for_connector(&first_input, encapsulating_path) - }), - } - } } diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index 22799dd14f..2acef7000f 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -129,6 +129,8 @@ --color-data-gradient-dim: #6c489b; --color-data-typography: #eea7a7; --color-data-typography-dim: #955252; + --color-data-invalid: var(--color-error-red); + --color-data-invalid-dim: color-mix(in srgb, var(--color-error-red) 75%, black); --color-none: white; --color-none-repeat: no-repeat; diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 4606d2c14d..113d0edadb 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -176,7 +176,7 @@ export type ContextMenuInformation = { contextMenuData: "CreateNode" | { type: "CreateNode"; compatibleType: string } | { nodeId: bigint; currentlyIsNode: boolean }; }; -export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color"; +export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color" | "Invalid"; export class FrontendGraphInput { readonly dataType!: FrontendGraphDataType; diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index 9eab6f2bc5..bcfccdb6c6 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -51,6 +51,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc std::cmp::Ordering { - self.name.cmp(&other.name) - } -} - -impl PartialOrd for TypeDescriptor { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - /// Graph runtime type information used for type inference. -#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, specta::Type, serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, specta::Type, serde::Serialize, serde::Deserialize)] pub enum Type { /// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete type. Generic(Cow<'static, str>), From 11ed1d511d01403b5f85ac5953eefd919bff7ff1 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 18 Nov 2025 16:41:36 -0800 Subject: [PATCH 4/6] Improve valid/complete types and disconnecting --- .../network_interface/resolved_types.rs | 87 +++++++++++-------- frontend/src/components/Editor.svelte | 4 +- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index 6163592456..f50f55e60d 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -137,9 +137,7 @@ impl NodeNetworkInterface { } } - /// Get the [`TypeSource`] for any InputConnector. - /// If the input is not compiled, then an Unknown or default from the definition is returned. - pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TypeSource { + fn input_type_not_invalid(&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"); }; @@ -147,10 +145,7 @@ impl NodeNetworkInterface { match input { NodeInput::Node { node_id, output_index } => { let output_connector = OutputConnector::node(*node_id, *output_index); - // First check if there is an error with this node or any protonodes it is connected to - if self.input_has_error(input_connector, network_path) { - return TypeSource::Invalid; - } + self.output_type(&output_connector, network_path) } @@ -168,21 +163,33 @@ 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 { + // First check if there is an error with this node or any protonodes it is connected to + if self.input_has_error(input_connector, network_path) { + return TypeSource::Invalid; + } + self.input_type_not_invalid(input_connector, network_path) + } + // 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::Unknown | TypeSource::Invalid => { - let mut ret = concrete!(()); - 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(value) = definition.node_template.document_node.inputs.get(*input_index).and_then(|input| input.as_value()) { - ret = value.ty(); - } - } - } - ret + // Pick a random type from the valid types + // TODO: Add a NodeInput::Indeterminate which can be resolved at compile time to be any type that prevents an error. This may require bidirectional typing. + self.complete_valid_input_types(input_connector, network_path) + .into_iter() + .min_by_key(|ty| ty.nested_type().to_string()) + .or_else(|| { + self.potential_valid_input_types(input_connector, network_path) + .into_iter() + .min_by_key(|ty| ty.nested_type().to_string()) + }) + .unwrap_or(concrete!(())) } TypeSource::Error(e) => { log::error!("Error getting tagged_value_from_input for {input_connector:?} {e}"); @@ -232,15 +239,21 @@ impl NodeNetworkInterface { return Vec::new(); }; let number_of_inputs = self.number_of_inputs(node_id, network_path); + log::debug!("{proto_node_identifier}"); implementations .iter() .filter_map(|(node_io, _)| { + log::debug!("types: {node_io:?}"); + // Check if this NodeIOTypes implementation is valid for the other inputs 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); + let input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path); + log::debug!("itertor index: {iterator_index} type: {input_type:?}"); // TODO: Fix type checking for different call arguments // For example a node input of (Footprint) -> Vector would not be compatible with a node that is called with () and returns Vector node_io.inputs.get(iterator_index).map(|ty| ty.nested_type()) == input_type.compiled_nested_type() }); + log::debug!("valid: {valid_implementation}"); + // If so, then return the input at the chosen index if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } }) .collect::>() @@ -253,24 +266,23 @@ impl NodeNetworkInterface { } /// Performs a downstream traversal to ensure input type will work in the full context of the graph. - pub fn complete_valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Result, String> { + pub fn complete_valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { match input_connector { InputConnector::Node { node_id, input_index } => { let Some(implementation) = self.implementation(node_id, network_path) else { - return Err(format!("Could not get node implementation for {:?} {} in complete_valid_input_types", network_path, *node_id)); + log::error!("Could not get node implementation for {:?} {} in complete_valid_input_types", network_path, *node_id); + return Vec::new(); }; 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 in complete_valid_input_types")); - }; - 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), + log::error!("Protonode {proto_node_identifier:?} not found in registry in complete_valid_input_types"); + return Vec::new(); }; + let valid_output_types = self.valid_output_types(&OutputConnector::node(*node_id, 0), network_path); - let valid_types = implementations + implementations .iter() .filter_map(|(node_io, _)| { if !valid_output_types.iter().any(|output_type| output_type.nested_type() == node_io.return_value.nested_type()) { @@ -278,7 +290,7 @@ impl NodeNetworkInterface { } 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); + let input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path); match input_type.compiled_nested_type() { Some(input_type) => node_io.inputs.get(iterator_index).is_some_and(|node_io_input_type| node_io_input_type.nested_type() == input_type), None => true, @@ -286,13 +298,9 @@ impl NodeNetworkInterface { }); 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()) + .collect::>() } + DocumentNodeImplementation::Extract => Vec::new(), } } InputConnector::Export(export_index) => { @@ -302,9 +310,10 @@ impl NodeNetworkInterface { // Valid types for the export are all types that can be fed into the render node let render_node = graphene_std::render_node::render::IDENTIFIER; let Some(implementations) = NODE_REGISTRY.get(&render_node) else { - return Err(format!("Protonode {render_node:?} not found in registry")); + log::error!("Protonode {render_node:?} not found in registry"); + return Vec::new(); }; - Ok(implementations.iter().map(|(types, _)| types.inputs[1].clone()).collect()) + implementations.iter().map(|(types, _)| types.inputs[1].clone()).collect() } } } @@ -341,12 +350,14 @@ impl NodeNetworkInterface { } /// The valid output types are all types that are valid for each downstream connection. - fn valid_output_types(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Result, String> { + fn valid_output_types(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Vec { let Some(outward_wires) = self.outward_wires(&network_path) else { - return Err("Could not get outward wires in valid_output_types".to_string()); + log::error!("Could not get outward wires in valid_output_types"); + return Vec::new(); }; let Some(inputs_from_import) = outward_wires.get(output_connector) else { - return Err("Could not get inputs from import in valid_output_types".to_string()); + log::error!("Could not get inputs from import in valid_output_types"); + return Vec::new(); }; let intersection = inputs_from_import @@ -359,6 +370,6 @@ impl NodeNetworkInterface { }) .unwrap_or_default(); - Ok(intersection.into_iter().collect::>()) + intersection.into_iter().collect::>() } } diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index 2acef7000f..af9acfb634 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -129,8 +129,8 @@ --color-data-gradient-dim: #6c489b; --color-data-typography: #eea7a7; --color-data-typography-dim: #955252; - --color-data-invalid: var(--color-error-red); - --color-data-invalid-dim: color-mix(in srgb, var(--color-error-red) 75%, black); + --color-data-invalid: #d6536e; // Same as --color-error-red + --color-data-invalid-dim: #a7324a; --color-none: white; --color-none-repeat: no-repeat; From c7a62df27807d6526de5bc986bc43779053b4f1a Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 18 Nov 2025 16:54:45 -0800 Subject: [PATCH 5/6] Code review --- .../graph_operation_message_handler.rs | 2 ++ .../document_node_derive.rs | 4 ++-- .../network_interface/resolved_types.rs | 15 ++++++--------- .../common_functionality/utility_functions.rs | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index d22eeda8b1..67dc579b85 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -142,6 +142,7 @@ impl MessageHandler> for skip_rerender: true, }); } + // Set the bottom input of the artboard back to artboard let bottom_input = NodeInput::value(TaggedValue::Artboard(Table::new()), true); network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]); @@ -149,6 +150,7 @@ impl MessageHandler> for // We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input. network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]); network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 1), primary_input, &[]); + // Set the bottom input of the artboard back to artboard let bottom_input = NodeInput::value(TaggedValue::Artboard(Table::new()), true); network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]); diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index 84e52e7022..6d5154ca54 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -18,12 +18,12 @@ fn traverse_node(node: &DocumentNode, node_metadata: &mut DocumentNodePersistent .persistent_metadata .node_metadata .get_mut(nested_node_id) - .expect("Network metadata must have corresponding node id"); + .expect("Network metadata must have corresponding node id"); traverse_node(nested_node, &mut nested_metadata.persistent_metadata); } } DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { - if let Some(metadata) = NODE_METADATA.lock().unwrap().get(&proto_node_identifier) { + if let Some(metadata) = NODE_METADATA.lock().unwrap().get(proto_node_identifier) { node_metadata.reference = Some(metadata.display_name.to_string()); } } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index f50f55e60d..2f5e0c2d0c 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -85,7 +85,7 @@ impl TypeSource { /// 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())) + self.compiled_nested_type().map(|ty| format!("type:{ty}")) } /// The type to display in the tooltip. @@ -239,20 +239,17 @@ impl NodeNetworkInterface { return Vec::new(); }; let number_of_inputs = self.number_of_inputs(node_id, network_path); - log::debug!("{proto_node_identifier}"); implementations .iter() .filter_map(|(node_io, _)| { - log::debug!("types: {node_io:?}"); // Check if this NodeIOTypes implementation is valid for the other inputs let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| { let input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path); - log::debug!("itertor index: {iterator_index} type: {input_type:?}"); // TODO: Fix type checking for different call arguments // For example a node input of (Footprint) -> Vector would not be compatible with a node that is called with () and returns Vector node_io.inputs.get(iterator_index).map(|ty| ty.nested_type()) == input_type.compiled_nested_type() }); - log::debug!("valid: {valid_implementation}"); + // If so, then return the input at the chosen index if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } }) @@ -313,7 +310,7 @@ impl NodeNetworkInterface { log::error!("Protonode {render_node:?} not found in registry"); return Vec::new(); }; - implementations.iter().map(|(types, _)| types.inputs[1].clone()).collect() + implementations.keys().map(|types| types.inputs[1].clone()).collect() } } } @@ -344,14 +341,14 @@ impl NodeNetworkInterface { if matches!(input_type, TypeSource::Invalid) { input_type = TypeSource::Unknown } - return input_type; + input_type } } } /// The valid output types are all types that are valid for each downstream connection. fn valid_output_types(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Vec { - let Some(outward_wires) = self.outward_wires(&network_path) else { + let Some(outward_wires) = self.outward_wires(network_path) else { log::error!("Could not get outward wires in valid_output_types"); return Vec::new(); }; @@ -363,7 +360,7 @@ impl NodeNetworkInterface { let intersection = inputs_from_import .clone() .iter() - .map(|input_connector| self.potential_valid_input_types(input_connector, &network_path).into_iter().collect::>()) + .map(|input_connector| self.potential_valid_input_types(input_connector, network_path).into_iter().collect::>()) .fold(None, |acc: Option>, set| match acc { Some(acc_set) => Some(acc_set.intersection(&set).cloned().collect()), None => Some(set), diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 40be20cce7..3c457aa605 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -582,7 +582,7 @@ pub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterfac 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.compiled_nested_type().as_deref() != Some(&concrete!(Table)) { + if output_type.compiled_nested_type() != Some(&concrete!(Table)) { return None; } From cde37a4c6e055c1a1814e355170e005cf416ee3a Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 18 Nov 2025 17:58:00 -0800 Subject: [PATCH 6/6] Return types on error --- .../node_graph/node_graph_message_handler.rs | 9 +++--- .../network_interface/resolved_types.rs | 6 ++-- editor/src/node_graph_executor.rs | 6 ++-- editor/src/node_graph_executor/runtime.rs | 8 ++--- node-graph/graph-craft/src/proto.rs | 2 -- .../src/dynamic_executor.rs | 30 +++++++++++++++++-- 6 files changed, 42 insertions(+), 19 deletions(-) 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 ace1da3dd9..a52fbd0765 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 @@ -873,13 +873,14 @@ impl<'a> MessageHandler> for NodeG 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()) + network_interface.root_node(breadcrumb_network_path).map(|root_node| root_node.to_connector()) } else { - network_interface.upstream_output_connector(clicked_input, selection_network_path) + network_interface.upstream_output_connector(clicked_input, breadcrumb_network_path) }; 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 = network_interface.input_type(clicked_input, breadcrumb_network_path).displayed_type(); + self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, breadcrumb_network_path); + + self.wire_in_progress_type = network_interface.output_type(&output_connector, breadcrumb_network_path).displayed_type(); return; } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index 2f5e0c2d0c..a3dccaacdc 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -179,17 +179,17 @@ impl NodeNetworkInterface { TypeSource::Compiled(compiled) => compiled, TypeSource::TaggedValue(value) => value, TypeSource::Unknown | TypeSource::Invalid => { - // Pick a random type from the valid types + // Pick a random type from the complete valid types // TODO: Add a NodeInput::Indeterminate which can be resolved at compile time to be any type that prevents an error. This may require bidirectional typing. self.complete_valid_input_types(input_connector, network_path) .into_iter() .min_by_key(|ty| ty.nested_type().to_string()) + // Pick a random type from the potential valid types .or_else(|| { self.potential_valid_input_types(input_connector, network_path) .into_iter() .min_by_key(|ty| ty.nested_type().to_string()) - }) - .unwrap_or(concrete!(())) + }).unwrap_or(concrete!(())) } TypeSource::Error(e) => { log::error!("Error getting tagged_value_from_input for {input_connector:?} {e}"); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 2b3ee20722..bc2be65f44 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -37,7 +37,7 @@ pub struct ExecutionResponse { #[derive(serde::Serialize, serde::Deserialize)] pub struct CompilationResponse { - result: Result, + result: Result, node_graph_errors: GraphErrors, } @@ -371,7 +371,7 @@ impl NodeGraphExecutor { NodeGraphUpdate::CompilationResponse(execution_response) => { let CompilationResponse { node_graph_errors, result } = execution_response; let type_delta = match result { - Err(e) => { + Err((incomplete_delta, e)) => { // Clear the click targets while the graph is in an un-renderable state document.network_interface.update_click_targets(HashMap::new()); @@ -380,7 +380,7 @@ impl NodeGraphExecutor { log::trace!("{e}"); responses.add(NodeGraphMessage::UpdateTypes { - resolved_types: Default::default(), + resolved_types: incomplete_delta, node_graph_errors, }); responses.add(NodeGraphMessage::SendGraph); diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index e9799ab02b..8f306dc46f 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -286,7 +286,7 @@ impl NodeRuntime { None } - async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { + async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { preprocessor::expand_network(&mut graph, &self.substitutions); let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone()); @@ -297,7 +297,7 @@ impl NodeRuntime { let c = Compiler {}; let proto_network = match c.compile_single(scoped_network) { Ok(network) => network, - Err(e) => return Err(e), + Err(e) => return Err((ResolvedDocumentNodeTypesDelta::default(), e)), }; self.monitor_nodes = proto_network .nodes @@ -307,9 +307,9 @@ impl NodeRuntime { .collect::>(); assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?"); - self.executor.update(proto_network).await.map_err(|e| { + self.executor.update(proto_network).await.map_err(|(types, e)| { self.node_graph_errors.clone_from(&e); - format!("{e:?}") + (types, format!("{e:?}")) }) } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 7f33a4fb7e..f2e3a3977f 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -539,7 +539,6 @@ impl ProtoNetwork { #[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum GraphErrorType { NodeNotFound(NodeId), - InputNodeNotFound(NodeId), UnexpectedGenerics { index: usize, inputs: Vec }, NoImplementations, NoConstructor, @@ -551,7 +550,6 @@ impl Debug for GraphErrorType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { GraphErrorType::NodeNotFound(id) => write!(f, "Input node {id} is not present in the typing context"), - GraphErrorType::InputNodeNotFound(id) => write!(f, "Input node {id} is not present in the typing context"), GraphErrorType::UnexpectedGenerics { index, inputs } => write!(f, "Generic inputs should not exist but found at {index}: {inputs:?}"), GraphErrorType::NoImplementations => write!(f, "No implementations found"), GraphErrorType::NoConstructor => write!(f, "No construct found for node"), diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index e6004f5e4c..bfa097ea1a 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -64,10 +64,34 @@ impl DynamicExecutor { /// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible. #[cfg_attr(debug_assertions, inline(never))] - pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result { + pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result { self.output = proto_network.output; - self.typing_context.update(&proto_network)?; - let (add, orphaned) = self.tree.update(proto_network, &self.typing_context).await?; + self.typing_context.update(&proto_network).map_err(|e| { + // If there is an error then get types that have been resolved before the error + let add = proto_network + .nodes + .iter() + .filter_map(|(id, node)| node.original_location.path.as_ref().map(|path| (path.clone().into_boxed_slice(), self.typing_context.infer(*id, node)))) + .take_while(|(_, r)| r.is_ok()) + .map(|(path, r)| { + let r = r.unwrap(); + ( + path, + NodeTypes { + inputs: r.inputs, + output: r.return_value, + }, + ) + }) + .collect::>(); + (ResolvedDocumentNodeTypesDelta { add, remove: Vec::new() }, e) + })?; + + let (add, orphaned) = self + .tree + .update(proto_network, &self.typing_context) + .await + .map_err(|e| (ResolvedDocumentNodeTypesDelta::default(), e))?; let old_to_remove = core::mem::replace(&mut self.orphaned_nodes, orphaned); let mut remove = Vec::with_capacity(old_to_remove.len() - self.orphaned_nodes.len().min(old_to_remove.len())); for node_id in old_to_remove {