-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix incorrect flattening behavior for hidden nodes #3759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
357cb32
1b1d12a
6cd1bcc
f0180fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -203,7 +203,7 @@ impl NodeNetworkInterface { | |
| concrete!(()) | ||
| } | ||
| }; | ||
| TaggedValue::from_type_or_none(&guaranteed_type) | ||
| TaggedValue::from_type(&guaranteed_type).expect("Failed to construct TaggedValue for identity type") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can crash when used on types from old documents which are no longer handled by the macro. A log::error! with the |
||
| } | ||
|
|
||
| /// A list of all valid input types for this specific node. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,32 @@ fn return_true() -> bool { | |
| true | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)] | ||
| pub struct HiddenNodeInput { | ||
| pub input_index: usize, | ||
| pub tagged_value: TaggedValue, | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect this to include the NodeId so it can be matched with the downstream (toward the export) node |
||
|
|
||
| #[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)] | ||
| pub enum Visible { | ||
| Passthrough, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passthrough means to use the current hiding logic, where it is replaced with a Passthrough node |
||
| Value(Type), //kept for backward compatibility | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. backward compatibility with what? This is a new enum. |
||
| TaggedValues(Vec<HiddenNodeInput>), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tagged values only occurs for nodes with no inputs, where valid input values must be chosen for the nodes it is connected to
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also it might be more clear to rename this to Hidden, since it is just behavior for Hidden nodes |
||
| } | ||
| impl Visible { | ||
| pub fn is_visible(&self) -> bool { | ||
| matches!(self, Visible::Passthrough) | ||
| } | ||
|
|
||
| pub fn is_hidden(&self) -> bool { | ||
| !self.is_visible() | ||
| } | ||
| } | ||
|
|
||
| fn return_visible_true() -> Visible { | ||
| Visible::Passthrough | ||
| } | ||
|
|
||
| /// An instance of a [`DocumentNodeDefinition`] that has been instantiated in a [`NodeNetwork`]. | ||
| /// Currently, when an instance is made, it lives all on its own without any lasting connection to the definition. | ||
| /// But we will want to change it in the future so it merely references its definition. | ||
|
|
@@ -50,8 +76,8 @@ pub struct DocumentNode { | |
| // 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. | ||
| #[serde(default = "return_true")] | ||
| pub visible: bool, | ||
| #[serde(default = "return_visible_true")] | ||
| pub visible: Visible, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be an option. None means visible, Some means hidden with the appropriate handling. |
||
| /// 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. | ||
| /// See [`ProtoNetwork::generate_stable_node_ids`] for details. | ||
| /// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph. | ||
|
|
@@ -94,7 +120,7 @@ impl Default for DocumentNode { | |
| inputs: Default::default(), | ||
| call_argument: concrete!(Context), | ||
| implementation: Default::default(), | ||
| visible: true, | ||
| visible: Visible::Passthrough, | ||
| skip_deduplication: Default::default(), | ||
| original_location: OriginalLocation::default(), | ||
| context_features: Default::default(), | ||
|
|
@@ -785,16 +811,84 @@ 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; | ||
| match node.visible.clone() { | ||
| Visible::Passthrough => {} | ||
|
|
||
| // Connect layer node to the group below | ||
| node.inputs.drain(1..); | ||
| node.call_argument = concrete!(()); | ||
| self.nodes.insert(id, node); | ||
| return; | ||
| Visible::Value(output_type) => { | ||
| let matching_input = node | ||
| .inputs | ||
| .iter() | ||
| .find_map(|input| match input { | ||
| NodeInput::Import { import_type, .. } if *import_type == output_type => Some(input.clone()), | ||
| NodeInput::Value { tagged_value, .. } if tagged_value.ty() == output_type => Some(input.clone()), | ||
| _ => None, | ||
| }) | ||
| .or_else(|| node.inputs.iter().find(|input| matches!(input, NodeInput::Node { .. })).cloned()); | ||
|
|
||
| if let Some(primary) = matching_input { | ||
| node.implementation = DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER); | ||
| node.call_argument = concrete!(()); | ||
| node.inputs.clear(); | ||
| node.inputs.push(primary); | ||
| } else { | ||
| let tagged = TaggedValue::from_type(&output_type).unwrap_or_else(|| TaggedValue::None); | ||
|
|
||
| node.implementation = DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::value::ClonedNode")); | ||
| node.call_argument = concrete!(()); | ||
| node.inputs.clear(); | ||
| node.inputs.push(NodeInput::value(tagged, false)); | ||
| } | ||
|
|
||
| self.nodes.insert(id, node); | ||
| return; | ||
| } | ||
|
|
||
| Visible::TaggedValues(tagged_values) => { | ||
| let passthrough_input = tagged_values | ||
| .iter() | ||
| .filter(|hidden_input| hidden_input.tagged_value.ty() != concrete!(())) | ||
| .find_map(|hidden_input| { | ||
| let input = node.inputs.get(hidden_input.input_index)?; | ||
| match input { | ||
| NodeInput::Node { .. } => Some(input.clone()), | ||
| NodeInput::Import { import_type, .. } if *import_type == hidden_input.tagged_value.ty() => Some(input.clone()), | ||
| NodeInput::Value { tagged_value, .. } if tagged_value.ty() == hidden_input.tagged_value.ty() => Some(input.clone()), | ||
| NodeInput::Import { .. } | NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Reflection(_) | NodeInput::Inline(_) => None, | ||
| } | ||
| }) | ||
| .or_else(|| { | ||
| tagged_values.iter().find_map(|hidden_input| { | ||
| let input = node.inputs.get(hidden_input.input_index)?; | ||
| match input { | ||
| NodeInput::Node { .. } | NodeInput::Import { .. } | NodeInput::Value { .. } => Some(input.clone()), | ||
| NodeInput::Scope(_) | NodeInput::Reflection(_) | NodeInput::Inline(_) => None, | ||
| } | ||
| }) | ||
| }); | ||
|
|
||
| if let Some(primary) = passthrough_input { | ||
| node.implementation = DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER); | ||
| node.call_argument = concrete!(()); | ||
| node.inputs.clear(); | ||
| node.inputs.push(primary); | ||
| } else { | ||
| let tagged = tagged_values | ||
| .iter() | ||
| .find(|hidden_input| hidden_input.tagged_value.ty() != concrete!(())) | ||
| .or_else(|| tagged_values.first()) | ||
| .map(|hidden_input| hidden_input.tagged_value.clone()) | ||
| .or_else(|| TaggedValue::from_type(&node.call_argument)) | ||
| .unwrap_or(TaggedValue::None); | ||
|
|
||
| node.implementation = DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::value::ClonedNode")); | ||
| node.call_argument = concrete!(()); | ||
| node.inputs.clear(); | ||
| node.inputs.push(NodeInput::value(tagged, false)); | ||
| } | ||
|
|
||
| self.nodes.insert(id, node); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| let path = node.original_location.path.clone().unwrap_or_default(); | ||
|
|
@@ -878,21 +972,35 @@ impl NodeNetwork { | |
|
|
||
| // Connect all nodes that were previously connected to this node to the nodes of the inner network | ||
| for (i, export) in inner_network.exports.into_iter().enumerate() { | ||
| if let NodeInput::Node { node_id, output_index, .. } = &export { | ||
| for deps in &node.original_location.dependants { | ||
| for dep in deps { | ||
| self.replace_node_inputs(*dep, (id, i), (*node_id, *output_index)); | ||
| match export { | ||
| NodeInput::Node { node_id, output_index } => { | ||
| for deps in &node.original_location.dependants { | ||
| for dep in deps { | ||
| self.replace_node_inputs(*dep, (id, i), (node_id, output_index)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if let Some(new_output_node) = self.nodes.get_mut(node_id) { | ||
| for dep in &node.original_location.dependants[i] { | ||
| new_output_node.original_location.dependants[*output_index].push(*dep); | ||
| if let Some(new_output_node) = self.nodes.get_mut(&node_id) { | ||
| for dep in &node.original_location.dependants[i] { | ||
| new_output_node.original_location.dependants[output_index].push(*dep); | ||
| } | ||
| } | ||
|
|
||
| self.replace_network_outputs(NodeInput::node(id, i), NodeInput::node(node_id, output_index)); | ||
| } | ||
| } | ||
|
|
||
| self.replace_network_outputs(NodeInput::node(id, i), export); | ||
| NodeInput::Import { import_index, .. } => { | ||
| let parent_input = node.inputs.get(import_index).expect("Import index must exist on parent"); | ||
|
|
||
| self.replace_network_outputs(NodeInput::node(id, i), parent_input.clone()); | ||
| } | ||
|
|
||
| NodeInput::Value { .. } => { | ||
| self.replace_network_outputs(NodeInput::node(id, i), export); | ||
| } | ||
|
|
||
| _ => {} | ||
| } | ||
| } | ||
|
|
||
| for node_id in new_nodes { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is generally wrong. For nodes with no inputs, we want to use the downstream traversal and type lookup solution. For nodes with any number of inputs they just become pass through nodes as is the current behavior.