diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 28c175044a..18c54c412e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ }, "ghcr.io/devcontainers/features/node:1": {} }, - "onCreateCommand": "cargo install wasm-pack cargo-watch cargo-about", + "onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.99", "customizations": { "vscode": { // NOTE: Keep this in sync with `.vscode/extensions.json` @@ -14,7 +14,6 @@ // Rust "rust-lang.rust-analyzer", "tamasfe.even-better-toml", - "serayuzgur.crates", // Web "dbaeumer.vscode-eslint", "svelte.svelte-vscode", diff --git a/.github/workflows/comment-clippy-warnings.yaml b/.github/workflows/comment-clippy-warnings.yaml index a9fb2ca866..07acaaea25 100644 --- a/.github/workflows/comment-clippy-warnings.yaml +++ b/.github/workflows/comment-clippy-warnings.yaml @@ -10,7 +10,8 @@ jobs: name: Run Clippy runs-on: ubuntu-latest # TODO(Keavon): Find a workaround (passing the output text to a separate action with permission to read the secrets?) that allows this to work on fork PRs - if: ${{ !github.event.pull_request.draft && !github.event.pull_request.head.repo.fork }} + if: false + # if: ${{ !github.event.pull_request.draft && !github.event.pull_request.head.repo.fork }} permissions: contents: read pull-requests: write diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 5550a2bad2..abe0dbc269 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -41,7 +41,7 @@ pub struct NodePropertiesContext<'a> { impl NodePropertiesContext<'_> { pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option> { let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else { - log::error!("Could not get input properties row in call_widget_override"); + log::error!("Could not get input properties row at the beginning of call_widget_override"); return None; }; if let Some(widget_override) = &input_properties_row.widget_override { @@ -2466,7 +2466,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(graphene_core::vector::VectorData), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -2479,7 +2479,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(bool), 4), // From the document node's parameters NodeInput::node(NodeId(0), 0), // From output 0 of SubpathSegmentLengthsNode ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_nodes::SamplePointsNode")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePointsNode")), manual_composition: Some(generic!(T)), ..Default::default() }, 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 91f138d7d6..cbd93183ab 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -240,8 +240,7 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, conte Separator::new(SeparatorType::Unrelated).widget_holder(), TextLabel::new("-") .tooltip(format!( - "This data can only be supplied through the\n\ - node graph because no widget exists for its type:\n\ + "This data can only be supplied through the node graph because no widget exists for its type:\n\ {}", concrete_type.name )) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 54e4c24cbf..fe4c94da79 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -14,7 +14,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; use graphene_core::text::{Font, TypesettingConfig}; use graphene_std::vector::style::{Fill, FillType, Gradient}; use interpreted_executor::dynamic_executor::IntrospectError; @@ -383,11 +383,25 @@ impl MessageHandler> for PortfolioMes document_is_saved, document_serialized_content, } => { + // TODO: Eventually remove this document upgrade code + // This big code block contains lots of hacky code for upgrading old documents to the new format + // It can be helpful to temporarily set `upgrade_from_before_editable_subgraphs` to true if it's desired to upgrade a piece of artwork to use fresh copies of all nodes let replace_implementations_from_definition = document_serialized_content.contains("node_output_index"); + // Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946 (see also `fn fix_nodes()` in `main.rs` of Graphene CLI) + let upgrade_from_before_returning_nested_click_targets = + document_serialized_content.contains("graphene_core::ConstructLayerNode") || document_serialized_content.contains("graphene_core::AddArtboardNode"); let upgrade_vector_manipulation_format = document_serialized_content.contains("ManipulatorGroupIds") && !document_name.contains("__DO_NOT_UPGRADE__"); let document_name = document_name.replace("__DO_NOT_UPGRADE__", ""); + const TEXT_REPLACEMENTS: [(&str, &str); 2] = [ + ("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePointsNode"), + ("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"), + ]; + let document_serialized_content = TEXT_REPLACEMENTS + .iter() + .fold(document_serialized_content, |document_serialized_content, (old, new)| document_serialized_content.replace(old, new)); + let document = DocumentMessageHandler::deserialize_document(&document_serialized_content).map(|mut document| { document.name.clone_from(&document_name); document @@ -407,9 +421,72 @@ impl MessageHandler> for PortfolioMes } }; - // TODO: Eventually remove this document upgrade code + const REPLACEMENTS: [(&str, &str); 36] = [ + ("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"), + ("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"), + ("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"), + ("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"), + ("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"), + ("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"), + ("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"), + ("graphene_core::ops::ConstructVector2", "graphene_core::ops::Vector2ValueNode"), + ("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"), + ("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"), + ("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"), + ("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"), + ("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"), + ("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"), + ("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"), + ("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"), + ("graphene_core::raster::IndexNode", "graphene_core::raster::adjustments::IndexNode"), + ("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"), + ("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"), + ("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"), + ("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"), + ("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"), + ("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"), + ("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"), + ("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"), + ("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"), + ("graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"), + ("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"), + ("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"), + ("graphene_core::vector::generator_nodes::PathGenerator", "graphene_core::vector::generator_nodes::PathNode"), + ("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"), + ( + "graphene_core::vector::generator_nodes::RegularPolygonGenerator", + "graphene_core::vector::generator_nodes::RegularPolygonNode", + ), + ("graphene_core::vector::generator_nodes::SplineGenerator", "graphene_core::vector::generator_nodes::SplineNode"), + ("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"), + ("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"), + ("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"), + ]; + for node_id in &document + .network_interface + .network_metadata(&[]) + .unwrap() + .persistent_metadata + .node_metadata + .keys() + .cloned() + .collect::>() + { + if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document.network_interface.network(&[]).unwrap().nodes.get(node_id).map(|node| node.implementation.clone()) { + for (old, new) in REPLACEMENTS { + let node_path_without_type_args = protonode_id.name.split('<').next(); + if node_path_without_type_args == Some(old) { + document + .network_interface + .replace_implementation(node_id, &[], DocumentNodeImplementation::ProtoNode(new.to_string().into())); + document.network_interface.set_manual_compostion(node_id, &[], Some(graph_craft::Type::Generic("T".into()))); + } + } + } + } + // Upgrade all old nodes to support editable subgraphs introduced in #1750 - if replace_implementations_from_definition { + if replace_implementations_from_definition || upgrade_from_before_returning_nested_click_targets { // This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed. // Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file for node_id in &document @@ -431,12 +508,13 @@ impl MessageHandler> for PortfolioMes .get(node_id) .and_then(|node| node.persistent_metadata.reference.as_ref()) { - let node_definition = resolve_document_node_type(reference).unwrap(); + let Some(node_definition) = resolve_document_node_type(reference) else { continue }; let default_definition_node = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation); document .network_interface .replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata); + document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition); } } } @@ -460,8 +538,6 @@ impl MessageHandler> for PortfolioMes continue; }; - // Upgrade Fill nodes to the format change in #1778 - // TODO: Eventually remove this document upgrade code let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else { continue; }; @@ -472,6 +548,7 @@ impl MessageHandler> for PortfolioMes }; let inputs_count = node.inputs.len(); + // Upgrade Fill nodes to the format change in #1778 if reference == "Fill" && inputs_count == 8 { let node_definition = resolve_document_node_type(reference).unwrap(); let document_node = node_definition.default_node_template().document_node; @@ -600,15 +677,8 @@ impl MessageHandler> for PortfolioMes .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), &[]); } - // Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946 - if reference == "Merge" || reference == "Artboard" { - let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); - let new_merge_node = node_definition.default_node_template(); - document.network_interface.replace_implementation(node_id, &[], new_merge_node.document_node.implementation) - } - // Upgrade artboard name being passed as hidden value input to "To Artboard" - if reference == "Artboard" { + if reference == "Artboard" && upgrade_from_before_returning_nested_click_targets { let label = document.network_interface.frontend_display_name(node_id, &[]); document .network_interface diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index d019e4bf4f..7d50b74f88 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -327,7 +327,7 @@ } function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string { - return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${value.dataType}`; + return value.resolvedType ? `Resolved Data:\n${value.resolvedType}` : `Unresolved Data ${value.dataType}`; } function validTypesText(value: FrontendGraphInput): string { @@ -502,7 +502,7 @@ style:--offset-top={position.y / 24} bind:this={outputs[0][index]} > - {`${dataTypeTooltip(outputMetadata)}\n${outputConnectedToText(outputMetadata)}`} + {`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`} {#if outputMetadata.connectedTo !== undefined} {:else} @@ -576,7 +576,7 @@ style:--offset-top={position.y / 24} bind:this={inputs[0][index]} > - {`${dataTypeTooltip(inputMetadata)}\n${inputConnectedToText(inputMetadata)}`} + {`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`} {#if inputMetadata.connectedTo !== undefined} {:else} @@ -670,8 +670,8 @@ bind:this={nodeElements[nodeIndex]} > {#if node.errors} - {node.errors} - {node.errors} + {node.errors} + {node.errors} {/if}
{#if $nodeGraph.thumbnails.has(node.id)} @@ -689,7 +689,7 @@ style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`} bind:this={outputs[nodeIndex + 1][0]} > - {`${dataTypeTooltip(node.primaryOutput)}\n${outputConnectedToText(node.primaryOutput)}`} + {`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`} {#if node.primaryOutput.connectedTo.length > 0} {#if primaryOutputConnectedToLayer(node)} @@ -712,7 +712,7 @@ bind:this={inputs[nodeIndex + 1][0]} > {#if node.primaryInput} - {`${dataTypeTooltip(node.primaryInput)}\n${validTypesText(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`} + {`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`} {/if} {#if node.primaryInput?.connectedTo !== undefined} @@ -737,7 +737,7 @@ style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`} bind:this={inputs[nodeIndex + 1][1]} > - {`${dataTypeTooltip(stackDataInput)}\n${validTypesText(stackDataInput)}\n${inputConnectedToText(stackDataInput)}`} + {`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`} {#if stackDataInput.connectedTo !== undefined} {:else} @@ -810,8 +810,8 @@ bind:this={nodeElements[nodeIndex]} > {#if node.errors} - {node.errors} - {node.errors} + {node.errors} + {node.errors} {/if}
@@ -844,7 +844,7 @@ style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`} bind:this={inputs[nodeIndex + 1][0]} > - {`${dataTypeTooltip(node.primaryInput)}\n${validTypesText(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`} + {`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`} {#if node.primaryInput.connectedTo !== undefined} {:else} @@ -864,7 +864,7 @@ style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`} bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]} > - {`${dataTypeTooltip(secondary)}\n${validTypesText(secondary)}\n${inputConnectedToText(secondary)}`} + {`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`} {#if secondary.connectedTo !== undefined} {:else} @@ -887,7 +887,7 @@ style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`} bind:this={outputs[nodeIndex + 1][0]} > - {`${dataTypeTooltip(node.primaryOutput)}\n${outputConnectedToText(node.primaryOutput)}`} + {`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`} {#if node.primaryOutput.connectedTo !== undefined} {:else} @@ -906,7 +906,7 @@ style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`} bind:this={outputs[nodeIndex + 1][outputIndex + (node.primaryOutput ? 1 : 0)]} > - {`${dataTypeTooltip(secondary)}\n${outputConnectedToText(secondary)}`} + {`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`} {#if secondary.connectedTo !== undefined} {:else} diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index a9bb333674..1f05cc08d0 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -20,7 +20,7 @@ pub mod types { pub type PixelLength = f64; /// Non negative pub type Length = f64; - /// 0.- 1. + /// 0 to 1 pub type Fraction = f64; pub type IntegerCount = u32; /// Int input with randomization button diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 2b5417f028..55971f2a75 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -110,7 +110,7 @@ impl NodeIOTypes { impl core::fmt::Debug for NodeIOTypes { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!( - "node({}) -> {}", + "node({}) → {}", [&self.call_argument].into_iter().chain(&self.inputs).map(|input| input.to_string()).collect::>().join(", "), self.return_value )) @@ -292,13 +292,13 @@ fn format_type(ty: &str) -> String { impl core::fmt::Debug for Type { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::Generic(arg0) => write!(f, "Generic({arg0})"), + Self::Generic(arg0) => write!(f, "Generic<{arg0}>"), #[cfg(feature = "type_id_logging")] - Self::Concrete(arg0) => write!(f, "Concrete({}, {:?})", arg0.name, arg0.id), + Self::Concrete(arg0) => write!(f, "Concrete<{}, {:?}>", arg0.name, arg0.id), #[cfg(not(feature = "type_id_logging"))] - Self::Concrete(arg0) => write!(f, "Concrete({})", format_type(&arg0.name)), - Self::Fn(arg0, arg1) => write!(f, "({arg0:?} -> {arg1:?})"), - Self::Future(arg0) => write!(f, "Future({arg0:?})"), + Self::Concrete(arg0) => write!(f, "Concrete<{}>", format_type(&arg0.name)), + Self::Fn(arg0, arg1) => write!(f, "{arg0:?} → {arg1:?}"), + Self::Future(arg0) => write!(f, "Future<{arg0:?}>"), } } } @@ -308,7 +308,7 @@ impl std::fmt::Display for Type { match self { Type::Generic(name) => write!(f, "{name}"), Type::Concrete(ty) => write!(f, "{}", format_type(&ty.name)), - Type::Fn(input, output) => write!(f, "({input} -> {output})"), + Type::Fn(input, output) => write!(f, "{input} → {output}"), Type::Future(ty) => write!(f, "Future<{ty}>"), } } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index a595ddc57a..53264bf832 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -588,7 +588,7 @@ impl ConcatElement for GraphicGroup { } } -#[node_macro::node(category(""))] +#[node_macro::node(category(""), path(graphene_core::vector))] async fn sample_points( #[implementations( (), @@ -815,7 +815,7 @@ async fn poisson_disk_points( result } -#[node_macro::node(category(""))] +#[node_macro::node(category(""), path(graphene_core::vector))] async fn subpath_segment_lengths( #[implementations( (), @@ -979,6 +979,8 @@ async fn morph( let target = target.eval(footprint).await; let mut result = VectorData::empty(); + let time = time.clamp(0., 1.); + // Lerp styles result.alpha_blending = if time < 0.5 { source.alpha_blending } else { target.alpha_blending }; result.style = source.style.lerp(&target.style, time); diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index cb40668bca..79d780e8fa 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -324,7 +324,7 @@ impl ProtoNetwork { } } - // TODO: Remsove + // TODO: Remove /// Create a hashmap with the list of nodes this proto network depends on/uses as inputs. pub fn collect_inwards_edges(&self) -> HashMap> { let mut edges: HashMap> = HashMap::new(); @@ -552,27 +552,19 @@ impl core::fmt::Debug for GraphErrorType { GraphErrorType::NoImplementations => write!(f, "No implementations found"), GraphErrorType::NoConstructor => write!(f, "No construct found for node"), GraphErrorType::InvalidImplementations { inputs, error_inputs } => { - let ordinal = |x: usize| match x.to_string().as_str() { - x if x.ends_with('1') && !x.ends_with("11") => format!("{x}st"), - x if x.ends_with('2') && !x.ends_with("12") => format!("{x}nd"), - x if x.ends_with('3') && !x.ends_with("13") => format!("{x}rd"), - x => format!("{x}th"), - }; - let format_index = |index: usize| if index == 0 { "primary".to_string() } else { format!("{} secondary", ordinal(index)) }; - let format_error = |(index, (real, expected)): &(usize, (Type, Type))| format!("• The {} input expected {} but found {}", format_index(*index), expected, real); + let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}", index + 1); let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::>().join("\n"); - let errors = error_inputs.iter().map(format_error_list).collect::>(); + let mut errors = error_inputs.iter().map(format_error_list).collect::>(); + errors.sort(); write!( f, - "Node graph type error! If this just appeared while editing the graph,\n\ - consider using undo to go back and try another way to connect the nodes.\n\ - \n\ - No node implementation exists for type:\n\ - ({inputs})\n\ + "This node isn't compatible with the com-\n\ + bination of types for the data it is given:\n\ + {inputs}\n\ \n\ - Caused by{}:\n\ + Each invalid input should be replaced by\n\ + data with one of these supported types:\n\ {}", - if errors.len() > 1 { " one of" } else { "" }, errors.join("\n") ) } @@ -679,7 +671,8 @@ impl TypingContext { }; // Get the node input type from the proto node declaration - let input = match node.input { + // TODO: When removing automatic composition, rename this to just `call_argument` + let primary_input_or_call_argument = match node.input { ProtoNodeInput::None => concrete!(()), ProtoNodeInput::ManualComposition(ref ty) => ty.clone(), ProtoNodeInput::Node(id) | ProtoNodeInput::NodeLambda(id) => { @@ -687,6 +680,7 @@ impl TypingContext { input.return_value.clone() } }; + let using_manual_composition = matches!(node.input, ProtoNodeInput::ManualComposition(_) | ProtoNodeInput::None); let impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?; if let Some(index) = inputs.iter().position(|p| { @@ -724,7 +718,7 @@ impl TypingContext { // List of all implementations that match the input types let valid_output_types = impls .keys() - .filter(|node_io| valid_subtype(&node_io.call_argument, &input) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_subtype(p1, p2))) + .filter(|node_io| valid_subtype(&node_io.call_argument, &primary_input_or_call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_subtype(p1, p2))) .collect::>(); // Attempt to substitute generic types with concrete types and save the list of results @@ -733,10 +727,10 @@ impl TypingContext { .map(|node_io| { collect_generics(node_io) .iter() - .try_for_each(|generic| check_generic(node_io, &input, &inputs, generic).map(|_| ())) + .try_for_each(|generic| check_generic(node_io, &primary_input_or_call_argument, &inputs, generic).map(|_| ())) .map(|_| { if let Type::Generic(out) = &node_io.return_value { - ((*node_io).clone(), check_generic(node_io, &input, &inputs, out).unwrap()) + ((*node_io).clone(), check_generic(node_io, &primary_input_or_call_argument, &inputs, out).unwrap()) } else { ((*node_io).clone(), node_io.return_value.clone()) } @@ -752,14 +746,18 @@ impl TypingContext { let mut best_errors = usize::MAX; let mut error_inputs = Vec::new(); for node_io in impls.keys() { - let current_errors = [&input] + let current_errors = [&primary_input_or_call_argument] .into_iter() .chain(&inputs) .cloned() .zip([&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned()) .enumerate() .filter(|(_, (p1, p2))| !valid_subtype(p1, p2)) - .map(|(index, ty)| (node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index), ty)) + .map(|(index, ty)| { + let i = node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index); + let i = if using_manual_composition { i } else { i + 1 }; + (i, ty) + }) .collect::>(); if current_errors.len() < best_errors { best_errors = current_errors.len(); @@ -769,7 +767,17 @@ impl TypingContext { error_inputs.push(current_errors); } } - let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); + let inputs = [&primary_input_or_call_argument] + .into_iter() + .chain(&inputs) + .enumerate() + // TODO: Make the following line's if statement conditional on being a call argument or primary input + .filter_map(|(i, t)| { + let i = if using_manual_composition { i } else { i + 1 }; + if i == 0 { None } else { Some(format!("• Input {i}: {t}")) } + }) + .collect::>() + .join("\n"); Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })]) } [(org_nio, _)] => { @@ -794,13 +802,13 @@ impl TypingContext { return Ok(org_nio.clone()); } } - let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); + let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); let valid = valid_output_types.into_iter().cloned().collect(); Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) } _ => { - let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); + let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); let valid = valid_output_types.into_iter().cloned().collect(); Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) } diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index 2ac5bc3927..6b3f85e340 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -79,7 +79,7 @@ fn init_logging() { } // Migrations are done in the editor which is unfortunately not available here. -// TODO: remove this and share migrations between the edtior and the CLI. +// TODO: remove this and share migrations between the editor and the CLI. fn fix_nodes(network: &mut NodeNetwork) { for node in network.nodes.values_mut() { match &mut node.implementation { diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 94b3886bf0..4072f0e42e 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -222,6 +222,7 @@ fn node_registry() -> HashMap