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 26268cb2..ca5b0303 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 @@ -692,7 +692,7 @@ fn import_usvg_path(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, modify_inputs.insert_vector(subpaths, layer, has_transform, path.fill().is_some(), path.stroke().is_some()); - if has_transform && let Some(transform_node_id) = modify_inputs.existing_network_node_id("Transform", false) { + if has_transform && let Some(transform_node_id) = modify_inputs.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, false) { transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, node_transform); } 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 0dc0fa67..f2e0f1ff 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -224,7 +224,9 @@ impl<'a> ModifyInputsContext<'a> { self.network_interface.move_node_to_chain_start(&shape_id, layer, &[], self.import); if include_transform { - let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template(); + let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) + .expect("Transform node does not exist") + .default_node_template(); let transform_id = NodeId::new(); self.network_interface.insert_node(transform_id, transform, &[]); self.network_interface.move_node_to_chain_start(&transform_id, layer, &[], self.import); @@ -266,7 +268,9 @@ impl<'a> ModifyInputsContext<'a> { Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)), Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)), ]); - let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template(); + let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) + .expect("Transform node does not exist") + .default_node_template(); let stroke = resolve_proto_node_type(graphene_std::vector_nodes::stroke::IDENTIFIER) .expect("Stroke node does not exist") .default_node_template(); @@ -305,7 +309,9 @@ impl<'a> ModifyInputsContext<'a> { } pub fn insert_image_data(&mut self, image: Image, layer: LayerNodeIdentifier) { - let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template(); + let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) + .expect("Transform node does not exist") + .default_node_template(); let image_node = resolve_proto_node_type(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) .expect("Image node does not exist") .node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::ImageData(image), false))]); @@ -506,7 +512,7 @@ impl<'a> ModifyInputsContext<'a> { pub fn gradient_line_set(&mut self, new_start: DVec2, new_end: DVec2) { let Some(output_layer) = self.get_output_layer() else { return }; - let transform_reference = DefinitionIdentifier::Network("Transform".into()); + let transform_reference = DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER); let upstream_transforms: Vec = self .network_interface .upstream_flow_back_from_nodes(vec![output_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow) @@ -553,7 +559,9 @@ impl<'a> ModifyInputsContext<'a> { if last_transform_value.abs_diff_eq(DAffine2::IDENTITY, 1e-6) { return; } - let Some(id) = self.existing_network_node_id("Transform", true) else { return }; + let Some(id) = self.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, true) else { + return; + }; id }; @@ -626,7 +634,7 @@ impl<'a> ModifyInputsContext<'a> { pub fn transform_change_with_parent(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) { // Get the existing upstream Transform node and its transform, if present, otherwise use the identity transform let (layer_transform, transform_node_id) = self - .existing_network_node_id("Transform", false) + .existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, false) .and_then(|transform_node_id| { let document_node = self.network_interface.document_network().nodes.get(&transform_node_id)?; Some((transform_utils::get_current_transform(&document_node.inputs), transform_node_id)) @@ -650,7 +658,7 @@ impl<'a> ModifyInputsContext<'a> { /// A new Transform node is created if one does not exist, unless it would be given the identity transform. pub fn transform_set(&mut self, transform: DAffine2, transform_in: TransformIn, skip_rerender: bool) { // Get the existing upstream Transform node, if present - let transform_node_id = self.existing_network_node_id("Transform", false); + let transform_node_id = self.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, false); // Compute the Transform node value so `transform_to_viewport` matches the target after re-render let final_transform = match transform_in { @@ -690,7 +698,7 @@ impl<'a> ModifyInputsContext<'a> { } // Create the Transform node - self.existing_network_node_id("Transform", true) + self.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, true) }) else { return; }; @@ -715,7 +723,7 @@ impl<'a> ModifyInputsContext<'a> { } pub fn brush_modify(&mut self, strokes: Vec) { - let Some(brush_node_id) = self.existing_network_node_id("Brush", true) else { + let Some(brush_node_id) = self.existing_proto_node_id(graphene_std::brush::brush::brush::IDENTIFIER, true) else { return; }; let strokes_table = strokes.into_iter().map(graphene_std::table::TableRow::new_from_element).collect(); 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 9e185f66..44ad9d8c 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 @@ -16,7 +16,6 @@ use graph_craft::ProtoNodeIdentifier; use graph_craft::concrete; use graph_craft::document::value::*; use graph_craft::document::*; -use graphene_std::brush::brush_cache::BrushCache; use graphene_std::extract_xy::XY; use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; use graphene_std::raster_types::{CPU, Raster}; @@ -133,46 +132,6 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy HashMap { let custom = vec![ - // TODO: Auto-generate this from its proto node macro - DocumentNodeDefinition { - identifier: "Passthrough", - category: "General", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), - inputs: vec![NodeInput::value(TaggedValue::None, true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("Content", "TODO").into()], - output_names: vec!["Out".to_string()], - ..Default::default() - }, - }, - description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."), - properties: None, - }, - // TODO: Auto-generate this from its proto node macro - DocumentNodeDefinition { - identifier: "Monitor", - category: "", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), - inputs: vec![NodeInput::value(TaggedValue::None, true)], - call_argument: generic!(T), - skip_deduplication: true, - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("In", "TODO").into()], - output_names: vec!["Out".to_string()], - ..Default::default() - }, - }, - description: Cow::Borrowed("The Monitor node is used by the editor to access the data flowing through it."), - properties: Some("monitor_properties"), - }, DocumentNodeDefinition { identifier: "Custom Node", category: "General", @@ -189,30 +148,6 @@ fn document_node_definitions() -> HashMap HashMap HashMap HashMap HashMap>), 0), - NodeInput::import(concrete!(Vec), 1), - NodeInput::import(concrete!(BrushCache), 2), - ], - implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER), - ..Default::default() - }] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![ - NodeInput::value(TaggedValue::Raster(Default::default()), true), - NodeInput::value(TaggedValue::BrushStrokeTable(Default::default()), false), - NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false), - ], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()], - output_names: vec!["Image".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, - // TODO: Auto-generate this from its proto node macro - DocumentNodeDefinition { - identifier: "Memoize", - category: "Debug", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("Image", "TODO").into()], - output_names: vec!["Image".to_string()], - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, #[cfg(feature = "gpu")] DocumentNodeDefinition { identifier: "Upload Texture", @@ -1462,7 +1268,7 @@ fn document_node_definitions() -> HashMap HashMap HashMap InputProperties { Ok(vec![LayoutGroup::row(widgets)]) }), ); + // Translation uses a Vec2 widget with X/Y labels and a "px" unit suffix + map.insert( + "transform_translation".to_string(), + Box::new(|node_id, index, context| { + Ok(vec![node_properties::vec2_widget( + ParameterWidgetsInfo::new(node_id, index, true, context), + "X", + "Y", + " px", + None, + false, + )]) + }), + ); + // Scale uses a Vec2 widget with W/H labels and an "x" unit suffix + map.insert( + "transform_scale".to_string(), + Box::new(|node_id, index, context| Ok(vec![node_properties::vec2_widget(ParameterWidgetsInfo::new(node_id, index, true, context), "W", "H", "x", None, false)])), + ); // Skew has a custom override that maps to degrees map.insert( "transform_skew".to_string(), 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 4d0c14fd..e1888788 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 @@ -22,8 +22,15 @@ pub(super) fn post_process_nodes(custom: Vec) -> HashMap }) .collect::>(); - // Add the rest of the protonodes from the macro + // Add the rest of the protonodes from the macro. + // Typed nodes are registered in `core_types::NODE_REGISTRY` via the macro's auto-generated `register_node` codegen. + // `skip_impl` nodes (e.g. Cache, Monitor) bypass that registration but are wired up manually in + // `interpreted_executor::node_registry::NODE_REGISTRY` via `async_node!`. We consult that extended registry as a + // fallback when deriving `call_argument` so it reflects the impls actually registered, which will usually be `Context`. + let extended_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY; let node_registry = NODE_REGISTRY.lock().unwrap(); + let empty_implementations: Vec<(NodeConstructor, NodeIOTypes)> = Vec::new(); + let context_type = concrete!(Context); for (id, metadata) in NODE_METADATA.lock().unwrap().iter() { let identifier = DefinitionIdentifier::ProtoNode(id.clone()); if definitions_map.contains_key(&identifier) { @@ -39,12 +46,25 @@ pub(super) fn post_process_nodes(custom: Vec) -> HashMap memoize: _, } = metadata; - let Some(implementations) = &node_registry.get(id) else { continue }; + let implementations = node_registry.get(id).unwrap_or(&empty_implementations); let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); - let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); - let input_type = if valid_inputs.len() > 1 { &const { generic!(D) } } else { &first_node_io.call_argument }; + let call_arguments: Vec<&Type> = if !implementations.is_empty() { + implementations.iter().map(|(_, io)| &io.call_argument).collect() + } else if let Some(impls) = extended_node_registry.get(id) { + impls.keys().map(|io| &io.call_argument).collect() + } else { + Vec::new() + }; + let valid_inputs: HashSet<&Type> = call_arguments.iter().copied().collect(); + let input_type = if valid_inputs.is_empty() { + &context_type + } else if valid_inputs.len() > 1 { + &const { generic!(D) } + } else { + call_arguments[0] + }; let inputs = preprocessor::node_inputs(fields, first_node_io); definitions_map.insert( @@ -83,16 +103,6 @@ pub(super) fn post_process_nodes(custom: Vec) -> HashMap ); } - // If any protonode does not have metadata then set its display name to its identifier string - for definition in definitions_map.values_mut() { - let metadata = NODE_METADATA.lock().unwrap(); - if let DocumentNodeImplementation::ProtoNode(id) = &definition.node_template.document_node.implementation - && !metadata.contains_key(id) - { - definition.node_template.persistent_node_metadata.display_name = definition.identifier.to_string(); - } - } - // Add the rest of the network nodes to the map and add the metadata for their internal protonodes for mut network_node in network_nodes { traverse_node(&network_node.node_template.document_node, &mut network_node.node_template.persistent_node_metadata, &definitions_map); 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 d701dff4..dfe09d8b 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 @@ -214,7 +214,11 @@ impl<'a> MessageHandler> for NodeG let mid_point = (network_interface.get_output_center(&output_connector, breadcrumb_network_path).unwrap() + network_interface.get_input_center(&input_connector, breadcrumb_network_path).unwrap()) / 2.; - let node_template = Box::new(resolve_proto_node_type(graphene_core::ops::identity::IDENTIFIER).unwrap().default_node_template()); + let Some(passthrough_definition) = resolve_proto_node_type(graphene_core::ops::passthrough::IDENTIFIER) else { + log::error!("Could not resolve passthrough node when wiring an export to an import"); + return; + }; + let node_template = Box::new(passthrough_definition.default_node_template()); let node_id = NodeId::new(); responses.add(NodeGraphMessage::InsertNode { node_id, node_template }); diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 45208025..325d2f74 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -126,7 +126,7 @@ impl DocumentMetadata { let local_transform = self.local_transforms.get(&layer.to_node()).copied(); let transform = local_transform.unwrap_or_else(|| { - let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network("Transform".into()), layer, network_interface); + let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), layer, network_interface); let transform_node = transform_node_id.and_then(|id| network_interface.document_node(&id, &[])); transform_node.map(|node| transform_utils::get_current_transform(node.inputs.as_slice())).unwrap_or_default() }); 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 b32eb9d6..d6ea96be 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 @@ -236,8 +236,8 @@ impl NodeNetworkInterface { } DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { let Some(implementations) = NODE_REGISTRY.get(proto_node_identifier) else { - // The compiler removes the identity node, so it's expected to be absent from the registry - if proto_node_identifier != &graphene_std::ops::identity::IDENTIFIER { + // The compiler removes the passthrough node, so it's expected to be absent from the registry + if proto_node_identifier != &graphene_std::ops::passthrough::IDENTIFIER { log::error!("Proto node `{proto_node_identifier:?}` not found in the node registry, in potential_valid_input_types"); } return Vec::new(); diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index 6ccd9f83..bf6068af 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -54,7 +54,7 @@ impl OriginalTransforms { /// Gets the transform from the most downstream transform node fn get_layer_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { - let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network("Transform".into()), layer, network_interface)?; + let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), layer, network_interface)?; let document_node = network_interface.document_network().nodes.get(&transform_node_id)?; Some(transform_utils::get_current_transform(&document_node.inputs)) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 5ad65069..e55ecc86 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -91,8 +91,9 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ aliases: &["graphene_core::ops::ExtractXyNode"], }, NodeReplacement { - node: graphene_std::ops::identity::IDENTIFIER, + node: graphene_std::ops::passthrough::IDENTIFIER, aliases: &[ + "graphene_core::ops::IdentityNode", "graphene_core::transform::CullNode", "graphene_core::transform::BoundlessFootprintNode", "graphene_core::transform::FreezeRealTimeNode", @@ -107,7 +108,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ aliases: &["graphene_core::memo::MonitorNode"], }, NodeReplacement { - node: graphene_std::memo::memo::IDENTIFIER, + node: graphene_std::memo::memoize::IDENTIFIER, aliases: &["graphene_core::memo::MemoNode", "graphene_core::memo::ImpureMemoNode"], }, NodeReplacement { @@ -1064,6 +1065,95 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ } } + // The "Brush" wrapper network was replaced with the `brush` proto node directly. Convert old `Network("Brush")` instances to the proto node, forwarding all 3 inputs (Background, Trace, Cache) one-to-one. + // This must run as a pre-pass before the recursive iteration below: replacing the outer Brush's network impl orphans its child paths, and the recursive iteration would log errors for those stale paths. + let brush_layers: Vec<(NodeId, Vec)> = document + .network_interface + .document_network() + .recursive_nodes() + .filter_map(|(node_id, _, path)| (document.network_interface.reference(node_id, &path) == Some(DefinitionIdentifier::Network("Brush".into()))).then_some((*node_id, path))) + .collect(); + for (node_id, network_path) in &brush_layers { + // Pre-load `outward_wires` so the chain-break check inside `set_input` resolves the original upstream→node wire from cache + // rather than triggering a fresh rebuild from the (already-mutated) post-`replace_inputs` state, which would orphan wires. + let _ = document.network_interface.outward_wires(network_path); + let new_reference = DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER); + let Some(definition) = resolve_document_node_type(&new_reference) else { continue }; + let mut node_template = definition.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + let Some(old_inputs) = document.network_interface.replace_inputs(node_id, network_path, &mut node_template) else { + continue; + }; + for (index, input) in old_inputs.iter().take(3).enumerate() { + document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + } + } + + // The "Transform" wrapper network was replaced with the `transform` proto node directly. Convert old `Network("Transform")` instances to the proto node, forwarding the 5 user-facing inputs (Value, Translation, Rotation, Scale, Skew) and dropping the legacy migration sentinels (Origin Offset, Scale Appearance) at indices 5 and 6 if present. + // Pre-pass for the same reason as the Brush migration above: replacing the outer Transform's network impl orphans its child paths. + let transform_layers: Vec<(NodeId, Vec, usize)> = document + .network_interface + .document_network() + .recursive_nodes() + .filter_map(|(node_id, node, path)| { + (document.network_interface.reference(node_id, &path) == Some(DefinitionIdentifier::Network("Transform".into()))).then_some((*node_id, path, node.inputs.len())) + }) + .collect(); + for (node_id, network_path, old_inputs_count) in &transform_layers { + // Pre-load `outward_wires` so the chain-break check inside `set_input` resolves the original upstream→node wire from cache + // rather than triggering a fresh rebuild from the (already-mutated) post-`replace_inputs` state, which would orphan wires. + let _ = document.network_interface.outward_wires(network_path); + let new_reference = DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER); + let Some(definition) = resolve_document_node_type(&new_reference) else { continue }; + let mut node_template = definition.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + let Some(old_inputs) = document.network_interface.replace_inputs(node_id, network_path, &mut node_template) else { + continue; + }; + // Forward the first 5 inputs (Value, Translation, Rotation, Scale, Skew); drop indices 5 and 6 if present. + for (index, input) in old_inputs.iter().take(5).enumerate() { + document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + } + + // Pre-2024 documents stored Transform with 6 inputs and used radians for Rotation and `tan(radians)` for Skew. Detect that legacy + // shape (no input at index 6) and convert the units to degrees so the values match what the new Properties panel widgets expect. + if *old_inputs_count == 6 { + match old_inputs.get(2) { + Some(NodeInput::Value { tagged_value, exposed }) => { + if let TaggedValue::F64(radians) = *tagged_value.clone().into_inner() { + let degrees = NodeInput::value(TaggedValue::F64(radians.to_degrees()), *exposed); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), degrees, network_path); + } + } + Some(NodeInput::Node { .. }) => { + // Wired upstream: splice in a Multiply node by 180/π that converts radians to degrees so the upstream value + // (which represented radians in the legacy format) reaches the now-degrees Rotation input correctly. + if let Some(multiply_node) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::multiply::IDENTIFIER)) { + let mut multiply_template = multiply_node.default_node_template(); + multiply_template.document_node.inputs[1] = NodeInput::value(TaggedValue::F64(180. / PI), false); + let multiply_node_id = NodeId::new(); + if let Some(transform_position) = document.network_interface.position_from_downstream_node(node_id, network_path) { + let multiply_position = transform_position + IVec2::new(-7, 1); + document.network_interface.insert_node(multiply_node_id, multiply_template, network_path); + document.network_interface.shift_absolute_node_position(&multiply_node_id, multiply_position, network_path); + document.network_interface.insert_node_between(&multiply_node_id, &InputConnector::node(*node_id, 2), 0, network_path); + } + } + } + _ => {} + } + + if let Some(NodeInput::Value { tagged_value, exposed }) = old_inputs.get(4) + && let TaggedValue::DVec2(old_value) = *tagged_value.clone().into_inner() + { + // The previous skew value stored `tan(radians)`, now it stores degrees directly. + let new_value = DVec2::new(old_value.x.atan().to_degrees(), old_value.y.atan().to_degrees()); + let new_input = NodeInput::value(TaggedValue::DVec2(new_value), *exposed); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), new_input, network_path); + } + } + } + // Apply upgrades to each unmodified node. let nodes = document .network_interface @@ -1677,79 +1767,6 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.add_import(TaggedValue::U32(0), false, 1, "Loop Level", "TODO", &node_path); } - // Migrate the Transform node to use degrees instead of radians - if reference == DefinitionIdentifier::Network("Transform".into()) && node.inputs.get(6).is_none() { - let mut node_template = resolve_network_node_type("Transform")?.default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - - // Value - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - // Translation - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - // Rotation - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - // Scale - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); - // Skew - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[4].clone(), network_path); - // Origin Offset - document - .network_interface - .set_input(&InputConnector::node(*node_id, 5), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), network_path); - // Scale Appearance - document - .network_interface - .set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path); - - // Migrate rotation from radians to degrees - match node.inputs.get(2)? { - NodeInput::Value { tagged_value, exposed } => { - // Read the existing Properties panel number value, which used to be in radians - let TaggedValue::F64(radians) = *tagged_value.clone().into_inner() else { return None }; - - // Convert the radians to degrees and set it back as the new input value - let degrees = NodeInput::value(TaggedValue::F64(radians.to_degrees()), *exposed); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), degrees, network_path); - } - NodeInput::Node { .. } => { - // Construct a new Multiply node for converting from degrees to radians - let Some(multiply_node) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::multiply::IDENTIFIER)) else { - log::error!("Could not get multiply node from definition when upgrading transform"); - return None; - }; - let mut multiply_template = multiply_node.default_node_template(); - multiply_template.document_node.inputs[1] = NodeInput::value(TaggedValue::F64(180. / PI), false); - - // Decide on the placement position of the new Multiply node - let multiply_node_id = NodeId::new(); - let Some(transform_position) = document.network_interface.position_from_downstream_node(node_id, network_path) else { - log::error!("Could not get positon for transform node {node_id}"); - return None; - }; - let multiply_position = transform_position + IVec2::new(-7, 1); - - // Insert the new Multiply node into the network directly before it's used - document.network_interface.insert_node(multiply_node_id, multiply_template, network_path); - document.network_interface.shift_absolute_node_position(&multiply_node_id, multiply_position, network_path); - document.network_interface.insert_node_between(&multiply_node_id, &InputConnector::node(*node_id, 2), 0, network_path); - } - _ => {} - }; - - // Migrate skew from radians to degrees - if let NodeInput::Value { tagged_value, exposed } = node.inputs.get(4)? { - // Read the existing Properties panel number value, which used to be in radians - let TaggedValue::DVec2(old_value) = *tagged_value.clone().into_inner() else { return None }; - - // The previous value stored the tangent of the displayed degrees. Now it stores the degrees, so take the arctan of it and convert to degrees. - let new_value = DVec2::new(old_value.x.atan().to_degrees(), old_value.y.atan().to_degrees()); - let new_input = NodeInput::value(TaggedValue::DVec2(new_value), *exposed); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), new_input, network_path); - } - } - // Upgrade the "Animation" node to add the "Rate" input if reference == DefinitionIdentifier::ProtoNode(graphene_std::animation::animation_time::IDENTIFIER) && inputs_count < 2 { let mut node_template = resolve_document_node_type(&reference)?.default_node_template(); @@ -1954,7 +1971,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], if let Some(downstream_input) = downstream { // Create a Transform node with translation = start - let Some(transform_node_type) = resolve_network_node_type("Transform") else { + let Some(transform_node_type) = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) else { log::error!("Transform node definition not found during Arrow migration"); return None; }; @@ -2015,7 +2032,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], if let Some(downstream_input) = downstream { // Create a Transform node with translation = start - let Some(transform_node_type) = resolve_network_node_type("Transform") else { + let Some(transform_node_type) = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) else { log::error!("Transform node definition not found during Line migration"); return None; }; @@ -2140,7 +2157,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler) -> Option<()> { // Collapse the legacy "Sample Polyline" wrapper network into the standalone `sample_polyline` proto node. // The proto node now computes per-bezpath segment lengths inline, so the wrapper's separate `subpath_segment_lengths` - // and `Memo` nodes are no longer needed. The 7 user-facing inputs are positionally identical between the + // and `Memoize` nodes are no longer needed. The 7 user-facing inputs are positionally identical between the // old wrapper and the new proto node. if let Some(DefinitionIdentifier::Network(name)) = document.network_interface.reference(node_id, network_path) && name == "Sample Polyline" @@ -2155,7 +2172,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne } // Collapse the legacy "Scatter Points" wrapper network into the standalone `scatter_points` proto node. - // The wrapper's trailing `Memo` node is now produced automatically by the `memoize` attribute on the + // The wrapper's trailing `Memoize` node is now produced automatically by the `memoize` attribute on the // proto node, so the wrapper itself is redundant. The 3 user-facing inputs are positionally identical // between the old wrapper and the new proto node. if let Some(DefinitionIdentifier::Network(name)) = document.network_interface.reference(node_id, network_path) @@ -2171,7 +2188,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne } // Collapse the legacy "Boolean Operation" wrapper network into the standalone `boolean_operation` proto node. - // The wrapper's trailing `Memo` node is now produced automatically by the `memoize` attribute on the + // The wrapper's trailing `Memoize` node is now produced automatically by the `memoize` attribute on the // proto node, so the wrapper itself is redundant. The 2 user-facing inputs are positionally identical // between the old wrapper and the new proto node. if let Some(DefinitionIdentifier::Network(name)) = document.network_interface.reference(node_id, network_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 9c62fcf7..6e26cd78 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -147,7 +147,7 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde // Add a transform node to ensure correct tooling modifications let transform_node_id = NodeId::new(); - let transform_node = document_node_definitions::resolve_network_node_type("Transform") + let transform_node = document_node_definitions::resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) .expect("Failed to create transform node") .default_node_template(); responses.add(NodeGraphMessage::InsertNode { @@ -251,7 +251,9 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { use graphene_std::transform_nodes::transform::*; - if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::Network("Transform".into()), TranslationInput::INDEX)? { + if let TaggedValue::DVec2(origin) = + NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), TranslationInput::INDEX)? + { Some(*origin) } else { None diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 81cd8ad6..f8fccc39 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -1,7 +1,7 @@ use super::tool_prelude::*; use crate::consts::DEFAULT_BRUSH_SIZE; use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform; -use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_network_node_type}; +use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_proto_node_type}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::FlowType; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; @@ -319,7 +319,7 @@ impl BrushToolData { continue; }; - if reference == DefinitionIdentifier::Network("Brush".into()) && node_id != layer.to_node() { + if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && node_id != layer.to_node() { let points_input = node.inputs.get(1)?; let Some(TaggedValue::BrushStrokeTable(strokes)) = points_input.as_value() else { continue }; self.strokes = strokes.iter_element_values().cloned().collect(); @@ -327,7 +327,7 @@ impl BrushToolData { return Some(layer); } - if reference == DefinitionIdentifier::Network("Transform".into()) { + if reference == DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER) { self.transform = get_current_transform(&node.inputs) * self.transform; } } @@ -478,7 +478,9 @@ impl Fsm for BrushToolFsmState { fn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque) -> LayerNodeIdentifier { responses.add(DocumentMessage::DeselectAllLayers); - let brush_node = resolve_network_node_type("Brush").expect("Brush node does not exist").default_node_template(); + let brush_node = resolve_proto_node_type(graphene_std::brush::brush::brush::IDENTIFIER) + .expect("Brush node does not exist") + .default_node_template(); let id = NodeId::new(); responses.add(GraphOperationMessage::NewCustomLayer { diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 61d72912..6f1fb6cc 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -344,7 +344,7 @@ struct GradientChainState { /// Resolve the gradient transform, type, and spread method by walking the chain feeding the layer. Transform composes all /// 'Transform' nodes. Type and spread method come from the closest-to-layer node of each kind, or the type default. fn read_gradient_chain_state(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> GradientChainState { - let transform_reference = DefinitionIdentifier::Network("Transform".into()); + let transform_reference = DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER); let gradient_type_reference = DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_type::IDENTIFIER); let spread_method_reference = DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::spread_method::IDENTIFIER); diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 5c784b55..74981681 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -767,7 +767,7 @@ mod test_transform_layer { let document = editor.active_document(); let network_interface = &document.network_interface; let _responses: VecDeque = VecDeque::new(); - let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network("Transform".into()), layer, network_interface)?; + let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), layer, network_interface)?; let document_node = network_interface.document_network().nodes.get(&transform_node_id)?; Some(transform_utils::get_current_transform(&document_node.inputs)) } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index d8306275..8d8c9c5e 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -49,7 +49,7 @@ pub struct DocumentNode { pub call_argument: Type, // A nested document network or a proto-node identifier. pub implementation: DocumentNodeImplementation, - /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step. + /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with a passthrough node during the graph flattening step. #[serde(default = "return_true")] pub visible: bool, /// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed. @@ -328,7 +328,7 @@ pub enum DocumentNodeImplementation { impl Default for DocumentNodeImplementation { fn default() -> Self { - Self::ProtoNode(graphene_core::ops::identity::IDENTIFIER) + Self::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER) } } @@ -433,7 +433,7 @@ pub struct OldDocumentNode { /// User chosen state for displaying this as a left-to-right node or bottom-to-top layer. Ensure the click target in the encapsulating network is updated when the node changes to a layer by using network.update_click_target(node_id). #[serde(default)] pub is_layer: bool, - /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step. + /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with a passthrough node during the graph flattening step. #[serde(default = "return_true")] pub visible: bool, /// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI. @@ -798,10 +798,10 @@ impl NodeNetwork { return; }; - // If the node is hidden, replace it with an identity node - let identity_node = DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER); - if !node.visible && node.implementation != identity_node { - node.implementation = identity_node; + // If the node is hidden, replace it with a passthrough node + let passthrough_node = DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER); + if !node.visible && node.implementation != passthrough_node { + node.implementation = passthrough_node; // Connect layer node to the group below node.inputs.drain(1..); @@ -967,12 +967,12 @@ impl NodeNetwork { } } - fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> { + fn remove_passthrough_node(&mut self, id: NodeId) -> Result<(), String> { let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone(); if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation - && *ident == graphene_core::ops::identity::IDENTIFIER + && *ident == graphene_core::ops::passthrough::IDENTIFIER { - assert_eq!(node.inputs.len(), 1, "Id node has more than one input"); + assert_eq!(node.inputs.len(), 1, "Passthrough node has more than one input"); if let NodeInput::Node { node_id, output_index, .. } = node.inputs[0] { let node_input_output_index = output_index; // TODO fix @@ -1015,20 +1015,20 @@ impl NodeNetwork { Ok(()) } - /// Strips out any [`graphene_core::ops::IdentityNode`]s that are unnecessary. - pub fn remove_redundant_id_nodes(&mut self) { - let id_nodes = self + /// Strips out any [`graphene_core::ops::PassthroughNode`]s that are unnecessary. + pub fn remove_redundant_passthrough_nodes(&mut self) { + let passthrough_nodes = self .nodes .iter() .filter(|(_, node)| { - matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &graphene_core::ops::identity::IDENTIFIER) + matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &graphene_core::ops::passthrough::IDENTIFIER) && node.inputs.len() == 1 && matches!(node.inputs[0], NodeInput::Node { .. }) }) .map(|(id, _)| *id) .collect::>(); - for id in id_nodes { - if let Err(e) = self.remove_id_node(id) { + for id in passthrough_nodes { + if let Err(e) = self.remove_passthrough_node(id) { log::warn!("{e}") } } @@ -1234,16 +1234,16 @@ mod test { #[test] fn extract_node() { - let id_node = DocumentNode { + let passthrough_node = DocumentNode { inputs: vec![], - implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER), ..Default::default() }; // TODO: Extend test cases to test nested network let mut extraction_network = NodeNetwork { exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ - id_node.clone(), + passthrough_node.clone(), DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], implementation: DocumentNodeImplementation::Extract, @@ -1260,7 +1260,7 @@ mod test { assert_eq!(extraction_network.nodes.len(), 1); let inputs = extraction_network.nodes.get(&NodeId(1)).unwrap().inputs.clone(); assert_eq!(inputs.len(), 1); - assert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(network), ..) if network == &id_node)); + assert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(network), ..) if network == &passthrough_node)); } #[test] @@ -1475,7 +1475,7 @@ mod test { } } - fn two_node_identity() -> NodeNetwork { + fn two_node_passthrough() -> NodeNetwork { NodeNetwork { exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], nodes: [ @@ -1483,7 +1483,7 @@ mod test { NodeId(1), DocumentNode { inputs: vec![NodeInput::import(concrete!(u32), 0)], - implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER), ..Default::default() }, ), @@ -1491,7 +1491,7 @@ mod test { NodeId(2), DocumentNode { inputs: vec![NodeInput::import(concrete!(u32), 1)], - implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER), ..Default::default() }, ), @@ -1510,7 +1510,7 @@ mod test { NodeId(1), DocumentNode { inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)], - implementation: DocumentNodeImplementation::Network(two_node_identity()), + implementation: DocumentNodeImplementation::Network(two_node_passthrough()), ..Default::default() }, ), @@ -1518,7 +1518,7 @@ mod test { NodeId(2), DocumentNode { inputs: vec![result_node_input], - implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER), ..Default::default() }, ), @@ -1543,7 +1543,7 @@ mod test { assert_eq!(result.exports[0], NodeInput::node(NodeId(11), 0), "The outer network output should be from a duplicated inner network"); let mut ids = result.nodes.keys().copied().collect::>(); ids.sort(); - assert_eq!(ids, vec![NodeId(11), NodeId(10010)], "Should only contain identity and values"); + assert_eq!(ids, vec![NodeId(11), NodeId(10010)], "Should only contain passthrough and values"); } // TODO: Write more tests diff --git a/node-graph/graph-craft/src/graphene_compiler.rs b/node-graph/graph-craft/src/graphene_compiler.rs index 5041b345..e9949e04 100644 --- a/node-graph/graph-craft/src/graphene_compiler.rs +++ b/node-graph/graph-craft/src/graphene_compiler.rs @@ -12,7 +12,7 @@ impl Compiler { network.flatten(id); } network.resolve_scope_inputs(); - network.remove_redundant_id_nodes(); + network.remove_redundant_passthrough_nodes(); // network.remove_dead_nodes(0); let proto_networks = network.into_proto_networks(); diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 3a3b55ae..6c2c36b8 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -139,7 +139,7 @@ pub struct ProtoNode { impl Default for ProtoNode { fn default() -> Self { Self { - identifier: graphene_core::ops::identity::IDENTIFIER, + identifier: graphene_core::ops::passthrough::IDENTIFIER, construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()), call_argument: concrete!(()), original_location: OriginalLocation::default(), @@ -317,14 +317,14 @@ impl ProtoNetwork { p.push(NodeId(10)) } - let memo_node_id = NodeId(self.nodes.len() as u64); + let memoize_node_id = NodeId(self.nodes.len() as u64); self.nodes.push(( - memo_node_id, + memoize_node_id, ProtoNode { construction_args: ConstructionArgs::Nodes(vec![node_id]), call_argument: concrete!(Context), - identifier: graphene_core::memo::memo::IDENTIFIER, + identifier: graphene_core::memo::memoize::IDENTIFIER, original_location: OriginalLocation { path: path.clone(), ..Default::default() @@ -352,7 +352,7 @@ impl ProtoNetwork { self.nodes.push(( nullification_node_id, ProtoNode { - construction_args: ConstructionArgs::Nodes(vec![memo_node_id, nullification_value_node_id]), + construction_args: ConstructionArgs::Nodes(vec![memoize_node_id, nullification_value_node_id]), call_argument: concrete!(Context), identifier: graphene_core::context_modification::context_modification::IDENTIFIER, original_location: OriginalLocation { diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index bfa097ea..904c83d6 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -216,7 +216,7 @@ impl BorrowTree { pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec, HashSet), GraphErrors> { let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect(); let mut new_nodes: Vec<_> = Vec::new(); - // TODO: Problem: When an identity node is connected directly to an export the first input to identity node is not added to the proto network, while the second input is. This means the primary input does not have a type. + // TODO: Problem: When a passthrough node is connected directly to an export the first input to the passthrough node is not added to the proto network, while the second input is. This means the primary input does not have a type. for (id, node) in proto_network.nodes { if !self.nodes.contains_key(&id) { new_nodes.push(node.original_location.path.clone().unwrap_or_default().into()); diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index be3c3a7d..68081de5 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -6,7 +6,7 @@ pub mod util; mod tests { use core_types::*; use futures::executor::block_on; - use graphene_core::ops::identity; + use graphene_core::ops::passthrough; #[test] fn double_number() { @@ -16,17 +16,17 @@ mod tests { let network = NodeNetwork { exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ - // Simple identity node taking a number as input from outside the graph + // Simple passthrough node taking a number as input from outside the graph ( NodeId(0), DocumentNode { inputs: vec![], call_argument: concrete!(u32), - implementation: DocumentNodeImplementation::ProtoNode(identity::IDENTIFIER), + implementation: DocumentNodeImplementation::ProtoNode(passthrough::IDENTIFIER), ..Default::default() }, ), - // An add node adding the result of the id node to its self + // An add node adding the result of the passthrough node to its self ( NodeId(1), DocumentNode { diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 9af0bc21..f6f140b4 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -140,86 +140,86 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]), #[cfg(target_family = "wasm")] async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextFeatures]), - // ========== - // MEMO NODES - // ========== - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ()]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => bool]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table>]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), + // ============= + // MEMOIZE NODES + // ============= + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => ()]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => bool]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table>]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Image]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), #[cfg(target_family = "wasm")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f32]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u32]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u64]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DVec2]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DAffine2]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Footprint]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => f64]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => f32]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => u32]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => u64]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DVec2]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => String]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DAffine2]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Footprint]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table>]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => BrushCache]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DocumentNode]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Box]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::QRCodeErrorCorrectionLevel]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text_nodes::StringCapitalization]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::NoiseType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::FractalType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularDistanceFunction]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularReturnType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::DomainWarpType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RelativeAbsolute]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::SelectiveColorChoice]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::RowsOrColumns]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table>]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Graphic]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => BrushCache]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DocumentNode]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Box]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::QRCodeErrorCorrectionLevel]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text_nodes::StringCapitalization]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::NoiseType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::FractalType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularDistanceFunction]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularReturnType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::DomainWarpType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RelativeAbsolute]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::SelectiveColorChoice]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::RowsOrColumns]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]), ]; // ============= // CONVERT NODES diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index e9877085..4d39e124 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -106,7 +106,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc + // For struct type instantiation: MemoizeNode let struct_type_params: Vec = data_field_generic_idents.iter().cloned().chain(node_generics.iter().cloned()).collect(); // Combined struct generic parameters with bounds for struct definition - // struct MemoNode + // struct MemoizeNode let struct_generic_params: Vec = data_field_generics.iter().map(|gp| quote!(#gp)).chain(node_generics.iter().map(|id| quote!(#id))).collect(); let input_ident = &input.pat_ident; @@ -622,8 +622,22 @@ fn generate_phantom_data<'a>(fn_generics: impl Iterator Result { + // On native, `register_node` and `register_metadata` run automatically via `#[ctor]`. + // On Wasm, `ctor` isn't available, so this `extern "C"` fn is invoked from JS to register the same way. + // `skip_impl` nodes don't generate a `register_node`, so the shim calls only `register_metadata` for them. + let registry_name = format_ident!("__node_registry_{}_{}", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name); + let register_node_call = if parsed.attributes.skip_impl { quote!() } else { quote!(register_node();) }; + let wasm_shim = quote! { + #[cfg(target_family = "wasm")] + #[unsafe(no_mangle)] + extern "C" fn #registry_name() { + #register_node_call + register_metadata(); + } + }; + if parsed.attributes.skip_impl { - return Ok(quote!()); + return Ok(wasm_shim); } let mut constructors = Vec::new(); @@ -708,8 +722,6 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st ) )); } - let registry_name = format_ident!("__node_registry_{}_{}", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name); - let native = quote! { #[cfg_attr(not(target_family = "wasm"), ctor)] fn register_node() { @@ -728,13 +740,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st Ok(quote! { #native - - #[cfg(target_family = "wasm")] - #[unsafe(no_mangle)] - extern "C" fn #registry_name() { - register_node(); - register_metadata(); - } + #wasm_shim }) } diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 329fb61c..bd813548 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -52,7 +52,7 @@ pub(crate) struct NodeFnAttributes { pub(crate) shader_node: Option, /// Custom serialization function path (e.g., "my_module::custom_serialize") pub(crate) serialize: Option, - /// Whether the preprocessor should add a Memo node after this node in the generated subnetwork + /// Whether the preprocessor should add a Memoize node after this node in the generated subnetwork pub(crate) memoize: bool, } @@ -379,7 +379,7 @@ impl Parse for NodeFnAttributes { .map_err(|_| Error::new_spanned(meta, "Expected a valid path for 'serialize', e.g., serialize(my_module::custom_serialize)"))?; serialize = Some(parsed_path); } - // Instructs the preprocessor to insert a Memo node after this node in the generated subnetwork, + // Instructs the preprocessor to insert a Memoize node after this node in the generated subnetwork, // caching its output across evaluations with identical inputs. // // Example usage: diff --git a/node-graph/nodes/brush/src/brush.rs b/node-graph/nodes/brush/src/brush.rs index e718df75..24a56fcb 100644 --- a/node-graph/nodes/brush/src/brush.rs +++ b/node-graph/nodes/brush/src/brush.rs @@ -187,30 +187,34 @@ pub fn blend_with_mode(background: TableRow>, foreground: TableRow>, + mut background: Table>, /// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles. - strokes: Table, + trace: Table, /// Internal cache data used to accelerate rendering of the brush content. cache: BrushCache, ) -> Table> { - if image.is_empty() { - image.push(TableRow::default()); + if background.is_empty() { + background.push(TableRow::default()); } // TODO: Find a way to handle more than one item - let table_row = image.clone_row(0).expect("Expected the one item we just pushed"); + let table_row = background.clone_row(0).expect("Expected the one item we just pushed"); let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false); let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] }; - let image_bbox = AxisAlignedBbox { start, end }; - let stroke_bbox = strokes.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); - let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) }; + let background_bbox = AxisAlignedBbox { start, end }; + let stroke_bbox = trace.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); + let bbox = if background_bbox.size().length() < 0.1 { + stroke_bbox + } else { + stroke_bbox.union(&background_bbox) + }; let background_bounds = bbox.to_transform(); - let mut draw_strokes: Vec<_> = strokes + let mut draw_strokes: Vec<_> = trace .iter_element_values() .filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)) .cloned() @@ -278,12 +282,12 @@ async fn brush( actual_image = blend_with_mode(actual_image, stroke_texture, stroke.style.blend_mode, (stroke.style.color.a() * 100.) as f64); } - let has_erase_or_restore_strokes = strokes.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)); + let has_erase_or_restore_strokes = trace.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)); if has_erase_or_restore_strokes { let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE); let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute(ATTR_TRANSFORM, background_bounds); - for stroke in strokes.into_iter().map(|row| row.into_element()) { + for stroke in trace.into_iter().map(|row| row.into_element()) { let mut brush_texture = cache.get_cached_brush(&stroke.style); if brush_texture.is_none() { let tex = create_brush_texture(&stroke.style).await; @@ -320,15 +324,15 @@ async fn brush( let clip: bool = actual_image.attribute_cloned_or_default(ATTR_CLIPPING_MASK); let layer: Table = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH); - *image.element_mut(0).unwrap() = actual_image.into_element(); - image.set_attribute(ATTR_TRANSFORM, 0, transform); - image.set_attribute(ATTR_BLEND_MODE, 0, blend_mode); - image.set_attribute(ATTR_OPACITY, 0, opacity); - image.set_attribute(ATTR_OPACITY_FILL, 0, fill); - image.set_attribute(ATTR_CLIPPING_MASK, 0, clip); - image.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer); + *background.element_mut(0).unwrap() = actual_image.into_element(); + background.set_attribute(ATTR_TRANSFORM, 0, transform); + background.set_attribute(ATTR_BLEND_MODE, 0, blend_mode); + background.set_attribute(ATTR_OPACITY, 0, opacity); + background.set_attribute(ATTR_OPACITY_FILL, 0, fill); + background.set_attribute(ATTR_CLIPPING_MASK, 0, clip); + background.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer); - image + background } pub fn blend_image_closure(foreground: TableRow>, mut background: TableRow>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow> { diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index eaacfc0e..cc5befe0 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -6,15 +6,19 @@ use std::hash::Hasher; use std::sync::Arc; use std::sync::Mutex; -/// Caches the output of a given node called with a specific input. +/// Helps speed up repeated renders in a computationally-heavy part of the node graph. /// -/// A cache miss occurs when the Option is None. In this case, the node evaluates the inner node and memoizes (stores) the result. -/// -/// A cache hit occurs when the Option is Some and has a stored hash matching the hash of the call argument. In this case, the node returns the cached value without re-evaluating the inner node. -/// -/// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache. -#[node_macro::node(category(""), path(graphene_core::memo), skip_impl)] -async fn memo(input: I, #[data] cache: Arc>>, node: impl Node) -> T { +/// Stores the last evaluated data that flowed through this node and immediately returns that data on subsequent renders if the context has not changed. +#[node_macro::node(category("General"), path(graphene_core::memo), skip_impl)] +async fn memoize(input: I, #[data] cache: Arc>>, content: impl Node) -> T { + // Caches the output of a given node called with a specific input. + // + // A cache miss occurs when the Option is None. In this case, the node evaluates the inner node and memoizes (stores) the result. + // + // A cache hit occurs when the Option is Some and has a stored hash matching the hash of the call argument. In this case, the node returns the cached value without re-evaluating the inner node. + // + // Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache. + let mut hasher = DefaultHasher::new(); input.cache_hash(&mut hasher); let hash = hasher.finish(); @@ -23,23 +27,23 @@ async fn memo(input: I, #[data return data; } - let value = node.eval(input).await; + let value = content.eval(input).await; *cache.lock().unwrap() = Some((hash, value.clone())); value } type MonitorValue = Arc>>>>; -/// Caches the output of the last graph evaluation for introspection. -#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), skip_impl)] +/// The Monitor node is used by the editor to access the data flowing through it. +#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"), skip_impl)] async fn monitor( input: I, #[allow(clippy::type_complexity)] #[data] io: MonitorValue, - node: impl Node, + content: impl Node, ) -> T { - let output = node.eval(input.clone()).await; + let output = content.eval(input.clone()).await; *io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() })); output } diff --git a/node-graph/nodes/gcore/src/ops.rs b/node-graph/nodes/gcore/src/ops.rs index 43ff7142..378d7356 100644 --- a/node-graph/nodes/gcore/src/ops.rs +++ b/node-graph/nodes/gcore/src/ops.rs @@ -4,12 +4,10 @@ use std::marker::PhantomData; // Re-export TypeNode from core-types for convenience pub use core_types::ops::TypeNode; -// TODO: Rename to "Passthrough" and make this the node that users use, not the one defined in document_node_definitions.rs -/// Passes-through the input value without changing it. -/// This is useful for rerouting wires for organization purposes. -#[node_macro::node(category(""), skip_impl)] -fn identity<'i, T: 'i + Send>(value: T) -> T { - value +/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. +#[node_macro::node(category("General"), skip_impl)] +fn passthrough<'i, T: 'i + Send>(_: impl Ctx, content: T) -> T { + content } #[node_macro::node(category(""), skip_impl)] @@ -27,7 +25,7 @@ mod test { use super::*; #[test] - pub fn identity_node() { - assert_eq!(identity(&4), &4); + pub fn passthrough_node() { + assert_eq!(passthrough((), &4), &4); } } diff --git a/node-graph/nodes/raster/src/dehaze.rs b/node-graph/nodes/raster/src/dehaze.rs index 31b3a053..4e7b799b 100644 --- a/node-graph/nodes/raster/src/dehaze.rs +++ b/node-graph/nodes/raster/src/dehaze.rs @@ -49,7 +49,7 @@ const TX: f32 = 0.1; // Paper: // TODO: Make this algorithm work with negative strength values fn dehaze_image(image: DynamicImage, strength: f64) -> DynamicImage { - // TODO: Break out this pair of steps into its own node, with a memoize node which caches the pair of outputs, so the strength can be adjusted without recomputing these two steps. + // TODO: Break out this pair of steps into its own node, with a Memoize node which caches the pair of outputs, so the strength can be adjusted without recomputing these two steps. let dark_channel = compute_dark_channel(&image); let atmospheric_light = estimate_atmospheric_light(&image, &dark_channel); diff --git a/node-graph/nodes/raster/src/std_nodes.rs b/node-graph/nodes/raster/src/std_nodes.rs index c72aa4d6..8590adf9 100644 --- a/node-graph/nodes/raster/src/std_nodes.rs +++ b/node-graph/nodes/raster/src/std_nodes.rs @@ -293,25 +293,40 @@ pub fn image(_: impl Ctx, _primary: (), image: Image) -> Table Table> { let footprint = ctx.footprint(); diff --git a/node-graph/nodes/transform/src/transform_nodes.rs b/node-graph/nodes/transform/src/transform_nodes.rs index 910ab692..87765875 100644 --- a/node-graph/nodes/transform/src/transform_nodes.rs +++ b/node-graph/nodes/transform/src/transform_nodes.rs @@ -10,7 +10,7 @@ use graphic_types::raster_types::{CPU, GPU, Raster}; use vector_types::GradientStops; /// Applies the specified transform to the input value, which may be a graphic type or another transform. -#[node_macro::node(category(""))] +#[node_macro::node(category("Math: Transform"))] async fn transform( ctx: impl Ctx + CloneVarArgs + ExtractAll + ModifyFootprint, #[implementations( @@ -24,10 +24,12 @@ async fn transform( Context -> Table, )] content: impl Node, Output = T>, - translation: DVec2, - rotation: f64, + #[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2, + #[widget(ParsedWidgetOverride::Custom = "transform_rotation")] rotation: f64, + #[widget(ParsedWidgetOverride::Custom = "transform_scale")] + #[default(1., 1.)] scale: DVec2, - skew: DVec2, + #[widget(ParsedWidgetOverride::Custom = "transform_skew")] skew: DVec2, ) -> T { let trs = DAffine2::from_scale_angle_translation(scale, rotation.to_radians(), translation); let skew = DAffine2::from_cols_array(&[1., skew.y.to_radians().tan(), skew.x.to_radians().tan(), 1., 0., 0.]); diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 961c084f..1e17c0f5 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -61,7 +61,7 @@ pub fn generate_node_substitutions() -> HashMap = node_io_types @@ -88,7 +88,7 @@ pub fn generate_node_substitutions() -> HashMap HashMap DocumentNode { inputs: vec![NodeInput::import(generic!(X), i)], - implementation: DocumentNodeImplementation::ProtoNode(identity_node.clone()), + implementation: DocumentNodeImplementation::ProtoNode(passthrough_node.clone()), visible: false, ..Default::default() }, @@ -127,17 +127,17 @@ pub fn generate_node_substitutions() -> HashMap HashMap Vec { fields .iter() - .zip(first_node_io.inputs.iter()) .enumerate() - .map(|(index, (field, node_io_ty))| { - let ty = field.default_type.as_ref().unwrap_or(node_io_ty); + .map(|(index, field)| { + // `skip_impl` nodes have no concrete implementations, so `first_node_io.inputs` is shorter than `fields`. + // When no type info is available for a field, fall through to the unspecified `None` value. + let Some(ty) = field.default_type.as_ref().or_else(|| first_node_io.inputs.get(index)) else { + return NodeInput::value(TaggedValue::None, true); + }; let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; match field.value_source {