mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 10:38:12 +08:00
Replace NodeTemplate's parallel node and metadata trees with a single flat recursive shape (#4377)
* Unify NodeTemplate into a single flat recursive shape with from_parts/into_parts as the storage split points * Unify the three sole-dependent traversals onto one is_sole_dependent kernel * Scan network exports for resource values when collecting used resources
This commit is contained in:
@@ -182,13 +182,8 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
|
|||||||
let mut resource_ids = HashSet::new();
|
let mut resource_ids = HashSet::new();
|
||||||
for item in &items {
|
for item in &items {
|
||||||
match item {
|
match item {
|
||||||
ClipboardItem::Layer(entry) => entry
|
ClipboardItem::Layer(entry) => entry.nodes.iter().for_each(|(_, template)| network_interface::collect_template_resources(template, &mut resource_ids)),
|
||||||
.nodes
|
ClipboardItem::Nodes(nodes) => nodes.iter().for_each(|(_, template)| network_interface::collect_template_resources(template, &mut resource_ids)),
|
||||||
.iter()
|
|
||||||
.for_each(|(_, template)| network_interface::collect_node_resources(&template.document_node, &mut resource_ids)),
|
|
||||||
ClipboardItem::Nodes(nodes) => nodes
|
|
||||||
.iter()
|
|
||||||
.for_each(|(_, template)| network_interface::collect_node_resources(&template.document_node, &mut resource_ids)),
|
|
||||||
ClipboardItem::Vector(_) | ClipboardItem::Resource(_) => {}
|
ClipboardItem::Vector(_) | ClipboardItem::Resource(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+38
-46
@@ -1,7 +1,6 @@
|
|||||||
use super::DocumentNodeDefinition;
|
use super::DocumentNodeDefinition;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputMetadata, NodeTemplate, NodeTemplateImplementation, WidgetOverride};
|
||||||
use graph_craft::document::*;
|
|
||||||
use graphene_std::registry::*;
|
use graphene_std::registry::*;
|
||||||
use graphene_std::*;
|
use graphene_std::*;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
@@ -14,7 +13,7 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
|
|||||||
let network_nodes = custom
|
let network_nodes = custom
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|definition| {
|
.filter_map(|definition| {
|
||||||
if let DocumentNodeImplementation::ProtoNode(proto_node_identifier) = &definition.node_template.document_node.implementation {
|
if let NodeTemplateImplementation::ProtoNode(proto_node_identifier) = &definition.node_template.implementation {
|
||||||
definitions_map.insert(DefinitionIdentifier::ProtoNode(proto_node_identifier.clone()), definition);
|
definitions_map.insert(DefinitionIdentifier::ProtoNode(proto_node_identifier.clone()), definition);
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
@@ -71,29 +70,21 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
|
|||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: display_name,
|
identifier: display_name,
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
inputs,
|
||||||
inputs,
|
call_argument: input_type.clone(),
|
||||||
call_argument: input_type.clone(),
|
implementation: NodeTemplateImplementation::ProtoNode(id.clone()),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
|
context_features: ContextDependencies::default(),
|
||||||
visible: true,
|
// TODO: Store information for input overrides in the node macro
|
||||||
skip_deduplication: false,
|
input_metadata: fields
|
||||||
context_features: ContextDependencies::default(),
|
.iter()
|
||||||
..Default::default()
|
.map(|f| match f.widget_override {
|
||||||
},
|
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
|
||||||
// TODO: Store information for input overrides in the node macro
|
RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||||
input_metadata: fields
|
RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||||
.iter()
|
})
|
||||||
.map(|f| match f.widget_override {
|
.collect(),
|
||||||
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
..Default::default()
|
||||||
RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
|
|
||||||
RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
|
||||||
RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
locked: false,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
category,
|
category,
|
||||||
description: Cow::Borrowed(description),
|
description: Cow::Borrowed(description),
|
||||||
@@ -104,13 +95,14 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
|
|||||||
|
|
||||||
// Add the rest of the network nodes to the map and add the metadata for their internal protonodes
|
// 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 {
|
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);
|
fill_proto_node_metadata(&mut network_node.node_template, &definitions_map);
|
||||||
|
|
||||||
// Set the reference to the node identifier
|
// Set the reference to the node identifier
|
||||||
if let Some(nested_metadata) = network_node.node_template.persistent_node_metadata.network_metadata.as_mut() {
|
if let NodeTemplateImplementation::Network(network_template) = &mut network_node.node_template.implementation {
|
||||||
nested_metadata.persistent_metadata.reference = Some(network_node.identifier.to_string());
|
network_template.reference = Some(network_node.identifier.to_string());
|
||||||
// If it is not a merge node, then set the display name to the identifier/reference
|
// If it is not a merge node, then set the display name to the identifier/reference
|
||||||
if network_node.identifier != "Merge" {
|
if network_node.identifier != "Merge" {
|
||||||
network_node.node_template.persistent_node_metadata.display_name = network_node.identifier.to_string();
|
network_node.node_template.display_name = network_node.identifier.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
definitions_map.insert(DefinitionIdentifier::Network(network_node.identifier.to_string()), network_node);
|
definitions_map.insert(DefinitionIdentifier::Network(network_node.identifier.to_string()), network_node);
|
||||||
@@ -119,27 +111,27 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
|
|||||||
definitions_map
|
definitions_map
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Traverses a document node template and metadata in parallel to add metadata to the protonodes
|
/// Recursively fills each nested proto node's editor metadata from its definition, preserving only the authored position.
|
||||||
fn traverse_node(node: &DocumentNode, node_metadata: &mut DocumentNodePersistentMetadata, definitions_map: &HashMap<DefinitionIdentifier, DocumentNodeDefinition>) {
|
fn fill_proto_node_metadata(node_template: &mut NodeTemplate, definitions_map: &HashMap<DefinitionIdentifier, DocumentNodeDefinition>) {
|
||||||
match &node.implementation {
|
match &mut node_template.implementation {
|
||||||
DocumentNodeImplementation::Network(node_network) => {
|
NodeTemplateImplementation::Network(network_template) => {
|
||||||
for (nested_node_id, nested_node) in node_network.nodes.iter() {
|
for nested_template in network_template.nodes.values_mut() {
|
||||||
let nested_metadata = node_metadata.network_metadata.as_mut().unwrap().persistent_metadata.node_metadata.get_mut(nested_node_id).unwrap();
|
fill_proto_node_metadata(nested_template, definitions_map);
|
||||||
traverse_node(nested_node, &mut nested_metadata.persistent_metadata, definitions_map);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentNodeImplementation::ProtoNode(id) => {
|
NodeTemplateImplementation::ProtoNode(id) => {
|
||||||
// Set all the metadata except the position to the proto node information from the macro
|
// If this lookup fails then the proto node id in the definition doesn't match what is generated by the macro
|
||||||
// TODO: Use options in the template to specify what you want to default and what you want to override
|
|
||||||
// If this fails then the proto node id in the definition doesn't match what is generated by the macro
|
|
||||||
let Some(definition) = definitions_map.get(&DefinitionIdentifier::ProtoNode(id.clone())) else {
|
let Some(definition) = definitions_map.get(&DefinitionIdentifier::ProtoNode(id.clone())) else {
|
||||||
// log::error!("Could not get definition for id {} when filling in protonode metadata for a custom node", id.clone());
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut new_metadata = definition.node_template.persistent_node_metadata.clone();
|
|
||||||
new_metadata.node_type_metadata = node_metadata.node_type_metadata.clone();
|
let definition_template = &definition.node_template;
|
||||||
*node_metadata = new_metadata
|
node_template.display_name = definition_template.display_name.clone();
|
||||||
|
node_template.input_metadata = definition_template.input_metadata.clone();
|
||||||
|
node_template.output_names = definition_template.output_names.clone();
|
||||||
|
node_template.locked = definition_template.locked;
|
||||||
|
node_template.pinned = definition_template.pinned;
|
||||||
}
|
}
|
||||||
DocumentNodeImplementation::Extract => {}
|
NodeTemplateImplementation::Extract => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
// A freshly added Text node carries no font, so give it the default font (registered like the Text tool does)
|
// A freshly added Text node carries no font, so give it the default font (registered like the Text tool does)
|
||||||
if node_type == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) {
|
if node_type == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) {
|
||||||
let font_resource_id = graph_craft::application_io::resource::ResourceId::new();
|
let font_resource_id = graph_craft::application_io::resource::ResourceId::new();
|
||||||
if let Some(font_input) = node_template.document_node.inputs.get_mut(graphene_std::text::text::FontInput::INDEX) {
|
if let Some(font_input) = node_template.inputs.get_mut(graphene_std::text::text::FontInput::INDEX) {
|
||||||
*font_input = NodeInput::value(TaggedValue::Resource(font_resource_id), false);
|
*font_input = NodeInput::value(TaggedValue::Resource(font_resource_id), false);
|
||||||
}
|
}
|
||||||
responses.add(DocumentMessage::Resource(ResourceMessage::AddFont {
|
responses.add(DocumentMessage::Resource(ResourceMessage::AddFont {
|
||||||
@@ -336,7 +336,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Ensure connection is to correct input of new node. If it does not have an input then do not connect
|
// Ensure connection is to correct input of new node. If it does not have an input then do not connect
|
||||||
if let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {
|
if let Some((input_index, _)) = node_template.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {
|
||||||
responses.add(NodeGraphMessage::CreateWire {
|
responses.add(NodeGraphMessage::CreateWire {
|
||||||
output_connector: *output_connector,
|
output_connector: *output_connector,
|
||||||
input_connector: InputConnector::node(node_id, input_index),
|
input_connector: InputConnector::node(node_id, input_index),
|
||||||
@@ -693,7 +693,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let center_of_selected_nodes_grid_space = IVec2::new((center_of_selected_nodes.x / 24. + 0.5).floor() as i32, (center_of_selected_nodes.y / 24. + 0.5).floor() as i32);
|
let center_of_selected_nodes_grid_space = IVec2::new((center_of_selected_nodes.x / 24. + 0.5).floor() as i32, (center_of_selected_nodes.y / 24. + 0.5).floor() as i32);
|
||||||
default_node_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::node(center_of_selected_nodes_grid_space - IVec2::new(3, 1));
|
default_node_template.node_type_metadata = NodeTypePersistentMetadata::node(center_of_selected_nodes_grid_space - IVec2::new(3, 1));
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::InsertNode {
|
responses.add(NodeGraphMessage::InsertNode {
|
||||||
node_id: encapsulating_node_id,
|
node_id: encapsulating_node_id,
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ mod queries;
|
|||||||
mod resolved_types;
|
mod resolved_types;
|
||||||
pub mod storage_metadata;
|
pub mod storage_metadata;
|
||||||
mod structure;
|
mod structure;
|
||||||
|
mod template;
|
||||||
mod types;
|
mod types;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod validation;
|
mod validation;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
pub use template::*;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
pub use view::{NetworkError, NetworkView};
|
pub use view::{NetworkError, NetworkView};
|
||||||
|
|
||||||
|
|||||||
@@ -193,41 +193,21 @@ impl NodeNetworkInterface {
|
|||||||
let mut not_sole_dependents = HashSet::new();
|
let mut not_sole_dependents = HashSet::new();
|
||||||
sole_dependents.insert(*stack_top);
|
sole_dependents.insert(*stack_top);
|
||||||
for upstream_node in self.upstream_flow_back_from_nodes(vec![*stack_top], network_path, FlowType::UpstreamFlow).collect::<Vec<_>>() {
|
for upstream_node in self.upstream_flow_back_from_nodes(vec![*stack_top], network_path, FlowType::UpstreamFlow).collect::<Vec<_>>() {
|
||||||
let mut stack = vec![upstream_node];
|
if sole_dependents.contains(&upstream_node) || not_sole_dependents.contains(&upstream_node) {
|
||||||
let mut is_sole_dependent = true;
|
continue;
|
||||||
while let Some(current_node) = stack.pop() {
|
|
||||||
if not_sole_dependents.contains(¤t_node) {
|
|
||||||
is_sole_dependent = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if !sole_dependents.contains(¤t_node) {
|
|
||||||
let mut has_outward_wire = false;
|
|
||||||
for output_index in 0..self.number_of_outputs(¤t_node, network_path) {
|
|
||||||
let Some(outward_wires) = self.outward_wires(network_path) else {
|
|
||||||
log::error!("Cannot load outward wires in load_stack_dependents");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(outward_wires) = outward_wires.get(&OutputConnector::node(current_node, output_index)) else {
|
|
||||||
log::error!("Cannot load outward wires in load_stack_dependents");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
for downstream_input in outward_wires {
|
|
||||||
has_outward_wire = true;
|
|
||||||
match downstream_input {
|
|
||||||
InputConnector::Node { node_id, .. } => stack.push(*node_id),
|
|
||||||
InputConnector::Export(_) => is_sole_dependent = false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !has_outward_wire {
|
|
||||||
is_sole_dependent = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !is_sole_dependent {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A path terminates at an already-verified sole dependent, and fails fast through a known non-sole node
|
||||||
|
let is_sole_dependent = self.is_sole_dependent(upstream_node, network_path, |downstream_node, _| {
|
||||||
|
if not_sole_dependents.contains(&downstream_node) {
|
||||||
|
SoleDependentStep::Escape
|
||||||
|
} else if sole_dependents.contains(&downstream_node) {
|
||||||
|
SoleDependentStep::Terminate
|
||||||
|
} else {
|
||||||
|
SoleDependentStep::Continue
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if is_sole_dependent {
|
if is_sole_dependent {
|
||||||
sole_dependents.insert(upstream_node);
|
sole_dependents.insert(upstream_node);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+24
@@ -73,6 +73,30 @@ async fn deleting_a_node_with_children_prunes_them_from_the_selection() {
|
|||||||
assert_invariants(&editor, "after deleting a node with children");
|
assert_invariants(&editor, "after deleting a node with children");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deleting_a_node_keeps_children_shared_with_other_nodes() {
|
||||||
|
let mut editor = EditorTestUtils::create();
|
||||||
|
editor.new_document().await;
|
||||||
|
|
||||||
|
let parent = editor.create_node_by_name(rectangle_definition()).await;
|
||||||
|
let sibling = editor.create_node_by_name(rectangle_definition()).await;
|
||||||
|
let shared_child = editor.create_node_by_name(rectangle_definition()).await;
|
||||||
|
|
||||||
|
let network_interface = &mut editor.active_document_mut().network_interface;
|
||||||
|
|
||||||
|
// Wire the same child into the secondary inputs of both nodes, then delete only the parent along with its children
|
||||||
|
network_interface.set_input(&InputConnector::node(parent, 1), NodeInput::node(shared_child, 0), &[]);
|
||||||
|
network_interface.set_input(&InputConnector::node(sibling, 1), NodeInput::node(shared_child, 0), &[]);
|
||||||
|
network_interface.delete_nodes(vec![parent], true, &[]);
|
||||||
|
|
||||||
|
let nodes = &network_interface.document_network().nodes;
|
||||||
|
assert!(!nodes.contains_key(&parent), "The deleted node itself should be gone");
|
||||||
|
assert!(nodes.contains_key(&shared_child), "A child shared with another node is not a sole dependent and should survive");
|
||||||
|
assert!(nodes.contains_key(&sibling), "The unrelated sibling should survive");
|
||||||
|
|
||||||
|
assert_invariants(&editor, "after deleting a node with a shared child");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn cyclic_connection_is_rejected_without_side_effects() {
|
async fn cyclic_connection_is_rejected_without_side_effects() {
|
||||||
let mut editor = EditorTestUtils::create();
|
let mut editor = EditorTestUtils::create();
|
||||||
|
|||||||
+37
-56
@@ -557,14 +557,13 @@ impl NodeNetworkInterface {
|
|||||||
log::error!("Could not get node in set_implementation");
|
log::error!("Could not get node in set_implementation");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let new_implementation = std::mem::take(&mut new_template.document_node.implementation);
|
let (new_implementation, new_network_metadata) = std::mem::take(&mut new_template.implementation).into_parts();
|
||||||
let _ = std::mem::replace(&mut node.implementation, new_implementation);
|
node.implementation = new_implementation;
|
||||||
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
|
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
|
||||||
log::error!("Could not get metadata in set_implementation");
|
log::error!("Could not get metadata in set_implementation");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.network_metadata);
|
metadata.persistent_metadata.network_metadata = new_network_metadata;
|
||||||
let _ = std::mem::replace(&mut metadata.persistent_metadata.network_metadata, new_metadata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces the inputs and corresponding metadata.
|
/// Replaces the inputs and corresponding metadata.
|
||||||
@@ -577,13 +576,13 @@ impl NodeNetworkInterface {
|
|||||||
log::error!("Could not get node in set_implementation");
|
log::error!("Could not get node in set_implementation");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let new_inputs = std::mem::take(&mut new_template.document_node.inputs);
|
let new_inputs = std::mem::take(&mut new_template.inputs);
|
||||||
let old_inputs = std::mem::replace(&mut node.inputs, new_inputs);
|
let old_inputs = std::mem::replace(&mut node.inputs, new_inputs);
|
||||||
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
|
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
|
||||||
log::error!("Could not get metadata in set_implementation");
|
log::error!("Could not get metadata in set_implementation");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.input_metadata);
|
let new_metadata = std::mem::take(&mut new_template.input_metadata);
|
||||||
let _ = std::mem::replace(&mut metadata.persistent_metadata.input_metadata, new_metadata);
|
let _ = std::mem::replace(&mut metadata.persistent_metadata.input_metadata, new_metadata);
|
||||||
Some(old_inputs)
|
Some(old_inputs)
|
||||||
}
|
}
|
||||||
@@ -597,7 +596,7 @@ impl NodeNetworkInterface {
|
|||||||
.reference(node_id, network_path)
|
.reference(node_id, network_path)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(resolve_document_node_type)
|
.and_then(resolve_document_node_type)
|
||||||
.and_then(|definition| definition.node_template.persistent_node_metadata.input_metadata.get(added_input_index))
|
.and_then(|definition| definition.node_template.input_metadata.get(added_input_index))
|
||||||
.cloned();
|
.cloned();
|
||||||
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return };
|
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return };
|
||||||
metadata.persistent_metadata.input_metadata.push(input_metadata.unwrap_or_default());
|
metadata.persistent_metadata.input_metadata.push(input_metadata.unwrap_or_default());
|
||||||
@@ -1023,12 +1022,13 @@ impl NodeNetworkInterface {
|
|||||||
node_template = self.map_ids(node_template, &old_node_id, &new_ids, network_path);
|
node_template = self.map_ids(node_template, &old_node_id, &new_ids, network_path);
|
||||||
// Insert node into network
|
// Insert node into network
|
||||||
let node_id = *new_ids.get(&old_node_id).unwrap();
|
let node_id = *new_ids.get(&old_node_id).unwrap();
|
||||||
|
let (document_node, persistent_metadata) = node_template.into_parts();
|
||||||
let Some(network) = self.network_mut(network_path) else {
|
let Some(network) = self.network_mut(network_path) else {
|
||||||
log::error!("Network not found in insert_node");
|
log::error!("Network not found in insert_node");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
network.nodes.insert(node_id, node_template.document_node);
|
network.nodes.insert(node_id, document_node);
|
||||||
self.transaction_modified();
|
self.transaction_modified();
|
||||||
|
|
||||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||||
@@ -1036,7 +1036,7 @@ impl NodeNetworkInterface {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let node_metadata = DocumentNodeMetadata {
|
let node_metadata = DocumentNodeMetadata {
|
||||||
persistent_metadata: node_template.persistent_node_metadata,
|
persistent_metadata,
|
||||||
transient_metadata: DocumentNodeTransientMetadata::default(),
|
transient_metadata: DocumentNodeTransientMetadata::default(),
|
||||||
};
|
};
|
||||||
network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata);
|
network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata);
|
||||||
@@ -1051,17 +1051,17 @@ impl NodeNetworkInterface {
|
|||||||
/// Used to insert a node template with no node/network inputs into the network and returns the a NodeTemplate with information from the previous node, if it existed.
|
/// Used to insert a node template with no node/network inputs into the network and returns the a NodeTemplate with information from the previous node, if it existed.
|
||||||
pub fn insert_node(&mut self, node_id: NodeId, node_template: NodeTemplate, network_path: &[NodeId]) -> Option<NodeTemplate> {
|
pub fn insert_node(&mut self, node_id: NodeId, node_template: NodeTemplate, network_path: &[NodeId]) -> Option<NodeTemplate> {
|
||||||
let has_node_or_network_input = node_template
|
let has_node_or_network_input = node_template
|
||||||
.document_node
|
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.all(|input| !(matches!(input, NodeInput::Node { .. }) || matches!(input, NodeInput::Import { .. })));
|
.all(|input| !(matches!(input, NodeInput::Node { .. }) || matches!(input, NodeInput::Import { .. })));
|
||||||
assert!(has_node_or_network_input, "Cannot insert node with node or network inputs. Use insert_node_group instead");
|
assert!(has_node_or_network_input, "Cannot insert node with node or network inputs. Use insert_node_group instead");
|
||||||
|
let (document_node, persistent_metadata) = node_template.into_parts();
|
||||||
let Some(network) = self.network_mut(network_path) else {
|
let Some(network) = self.network_mut(network_path) else {
|
||||||
log::error!("Network not found in insert_node");
|
log::error!("Network not found in insert_node");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let previous_node = network.nodes.insert(node_id, node_template.document_node);
|
let previous_node = network.nodes.insert(node_id, document_node);
|
||||||
self.transaction_modified();
|
self.transaction_modified();
|
||||||
|
|
||||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||||
@@ -1069,7 +1069,7 @@ impl NodeNetworkInterface {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let node_metadata = DocumentNodeMetadata {
|
let node_metadata = DocumentNodeMetadata {
|
||||||
persistent_metadata: node_template.persistent_node_metadata,
|
persistent_metadata,
|
||||||
transient_metadata: DocumentNodeTransientMetadata::default(),
|
transient_metadata: DocumentNodeTransientMetadata::default(),
|
||||||
};
|
};
|
||||||
let previous_metadata = network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata);
|
let previous_metadata = network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata);
|
||||||
@@ -1077,18 +1077,26 @@ impl NodeNetworkInterface {
|
|||||||
self.unload_all_nodes_bounding_box(network_path);
|
self.unload_all_nodes_bounding_box(network_path);
|
||||||
self.unload_node_click_targets(&node_id, network_path);
|
self.unload_node_click_targets(&node_id, network_path);
|
||||||
|
|
||||||
previous_node.zip(previous_metadata).map(|(document_node, node_metadata)| NodeTemplate {
|
previous_node
|
||||||
document_node,
|
.zip(previous_metadata)
|
||||||
persistent_node_metadata: node_metadata.persistent_metadata,
|
.map(|(document_node, node_metadata)| NodeTemplate::from_parts(document_node, node_metadata.persistent_metadata))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes all nodes in `node_ids` and any sole dependents in the horizontal chain if the node to delete is a layer node.
|
/// Deletes all nodes in `node_ids` and any sole dependents in the horizontal chain if the node to delete is a layer node.
|
||||||
pub fn delete_nodes(&mut self, nodes_to_delete: Vec<NodeId>, delete_children: bool, network_path: &[NodeId]) {
|
pub fn delete_nodes(&mut self, nodes_to_delete: Vec<NodeId>, delete_children: bool, network_path: &[NodeId]) {
|
||||||
let Some(outward_wires) = self.outward_wires(network_path).cloned() else {
|
if self.outward_wires(network_path).is_none() {
|
||||||
log::error!("Could not get outward wires in delete_nodes");
|
log::error!("Could not get outward wires in delete_nodes");
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// Layer membership is fixed during the expansion phase, so gather it once for the sole-dependent closure
|
||||||
|
let layer_nodes = self
|
||||||
|
.nested_network(network_path)
|
||||||
|
.map(|network| network.nodes.keys().copied().collect::<Vec<_>>())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|candidate| self.is_layer(candidate, network_path))
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
let mut delete_nodes = HashSet::new();
|
let mut delete_nodes = HashSet::new();
|
||||||
for node_id in &nodes_to_delete {
|
for node_id in &nodes_to_delete {
|
||||||
@@ -1109,45 +1117,18 @@ impl NodeNetworkInterface {
|
|||||||
upstream_nodes.push(upstream_node);
|
upstream_nodes.push(upstream_node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// For each potential child perform a complete downstream traversal, ending at either a node in the `delete_nodes` set (excluding layer bottom inputs), the output, or a dead end.
|
|
||||||
// If the output node is eventually reached, then it is not a sole dependent and will not be deleted
|
// A path terminates when absorbed by another node marked for deletion, except through a layer's bottom input, which is stack flow to walk through.
|
||||||
let mut stack = vec![upstream_node];
|
// Reaching the primary input of the node being deleted means this is the stack continuation rather than a child, so it must survive.
|
||||||
let mut can_delete = true;
|
let can_delete = self.is_sole_dependent(upstream_node, network_path, |downstream_node, input_index| {
|
||||||
while let Some(current_node) = stack.pop() {
|
if downstream_node == *node_id && input_index == 0 {
|
||||||
let mut is_dead_end = true;
|
SoleDependentStep::Escape
|
||||||
for output_connector in (0..self.number_of_outputs(¤t_node, network_path)).map(|output_index| OutputConnector::node(current_node, output_index)) {
|
} else if delete_nodes.contains(&downstream_node) && !(input_index == 0 && layer_nodes.contains(&downstream_node)) {
|
||||||
let Some(downstream_nodes) = outward_wires.get(&output_connector) else { continue };
|
SoleDependentStep::Terminate
|
||||||
if !downstream_nodes.is_empty() {
|
} else {
|
||||||
is_dead_end = false
|
SoleDependentStep::Continue
|
||||||
}
|
|
||||||
for downstream_node in downstream_nodes {
|
|
||||||
if let InputConnector::Node { node_id: downstream_id, input_index } = downstream_node {
|
|
||||||
// If the downstream node is not in the delete nodes set, then continue iterating
|
|
||||||
// If the downstream node is the bottom input of a layer then continue iterating
|
|
||||||
if !delete_nodes.contains(downstream_id) || (*input_index == 0 && self.is_layer(downstream_id, network_path)) {
|
|
||||||
stack.push(*downstream_id);
|
|
||||||
}
|
|
||||||
// If the traversal reaches the primary input of the node to delete then do not delete it
|
|
||||||
if node_id == downstream_id && *input_index == 0 {
|
|
||||||
can_delete = false;
|
|
||||||
stack = Vec::new();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the traversal reaches the export, then the current node is not a sole dependent and cannot be deleted
|
|
||||||
else {
|
|
||||||
can_delete = false;
|
|
||||||
stack = Vec::new();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// If there are no outward wires, then we have reached a dead end, and the node cannot be deleted
|
});
|
||||||
if is_dead_end {
|
|
||||||
can_delete = false;
|
|
||||||
stack = Vec::new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if can_delete {
|
if can_delete {
|
||||||
delete_nodes.insert(upstream_node);
|
delete_nodes.insert(upstream_node);
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ impl NodeNetworkInterface {
|
|||||||
log::error!("Could not get position in create_node_template");
|
log::error!("Could not get position in create_node_template");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
match &mut node_template.persistent_node_metadata.node_type_metadata {
|
match &mut node_template.node_type_metadata {
|
||||||
NodeTypePersistentMetadata::Layer(layer_metadata) => layer_metadata.position = LayerPosition::Absolute(position),
|
NodeTypePersistentMetadata::Layer(layer_metadata) => layer_metadata.position = LayerPosition::Absolute(position),
|
||||||
NodeTypePersistentMetadata::Node(node_metadata) => node_metadata.position = NodePosition::Absolute(position),
|
NodeTypePersistentMetadata::Node(node_metadata) => node_metadata.position = NodePosition::Absolute(position),
|
||||||
};
|
};
|
||||||
@@ -198,14 +198,14 @@ impl NodeNetworkInterface {
|
|||||||
log::error!("Could not get position in create_node_template");
|
log::error!("Could not get position in create_node_template");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
node_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::Node(NodePersistentMetadata {
|
node_template.node_type_metadata = NodeTypePersistentMetadata::Node(NodePersistentMetadata {
|
||||||
position: NodePosition::Absolute(position),
|
position: NodePosition::Absolute(position),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift all absolute nodes 2 to the right and 2 down
|
// Shift all absolute nodes 2 to the right and 2 down
|
||||||
// TODO: Remove 2x2 offset and replace with layout system to find space for new node
|
// TODO: Remove 2x2 offset and replace with layout system to find space for new node
|
||||||
match &mut node_template.persistent_node_metadata.node_type_metadata {
|
match &mut node_template.node_type_metadata {
|
||||||
NodeTypePersistentMetadata::Layer(layer_metadata) => {
|
NodeTypePersistentMetadata::Layer(layer_metadata) => {
|
||||||
if let LayerPosition::Absolute(position) = &mut layer_metadata.position {
|
if let LayerPosition::Absolute(position) = &mut layer_metadata.position {
|
||||||
*position += IVec2::new(2, 2)
|
*position += IVec2::new(2, 2)
|
||||||
@@ -228,7 +228,7 @@ impl NodeNetworkInterface {
|
|||||||
if self.is_layer(&old_id, network_path) {
|
if self.is_layer(&old_id, network_path) {
|
||||||
for valid_upstream_chain_node in self.valid_upstream_chain_nodes(&InputConnector::node(old_id, 1), network_path) {
|
for valid_upstream_chain_node in self.valid_upstream_chain_nodes(&InputConnector::node(old_id, 1), network_path) {
|
||||||
if let Some(node_template) = new_nodes.iter_mut().find_map(|(_, old_id, template)| (*old_id == valid_upstream_chain_node).then_some(template)) {
|
if let Some(node_template) = new_nodes.iter_mut().find_map(|(_, old_id, template)| (*old_id == valid_upstream_chain_node).then_some(template)) {
|
||||||
match &mut node_template.persistent_node_metadata.node_type_metadata {
|
match &mut node_template.node_type_metadata {
|
||||||
NodeTypePersistentMetadata::Node(node_metadata) => node_metadata.position = NodePosition::Chain,
|
NodeTypePersistentMetadata::Node(node_metadata) => node_metadata.position = NodePosition::Chain,
|
||||||
NodeTypePersistentMetadata::Layer(_) => log::error!("Node cannot be a layer"),
|
NodeTypePersistentMetadata::Layer(_) => log::error!("Node cannot be a layer"),
|
||||||
};
|
};
|
||||||
@@ -248,7 +248,7 @@ impl NodeNetworkInterface {
|
|||||||
///
|
///
|
||||||
/// If the node is not in the hashmap then a default input is found based on the compiled network, using the node_id passed as a parameter
|
/// If the node is not in the hashmap then a default input is found based on the compiled network, using the node_id passed as a parameter
|
||||||
pub fn map_ids(&mut self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap<NodeId, NodeId>, network_path: &[NodeId]) -> NodeTemplate {
|
pub fn map_ids(&mut self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap<NodeId, NodeId>, network_path: &[NodeId]) -> NodeTemplate {
|
||||||
for (input_index, input) in node_template.document_node.inputs.iter_mut().enumerate() {
|
for (input_index, input) in node_template.inputs.iter_mut().enumerate() {
|
||||||
if let &mut NodeInput::Node { node_id: id, output_index } = input {
|
if let &mut NodeInput::Node { node_id: id, output_index } = input {
|
||||||
if let Some(&new_id) = new_ids.get(&id) {
|
if let Some(&new_id) = new_ids.get(&id) {
|
||||||
*input = NodeInput::Node { node_id: new_id, output_index };
|
*input = NodeInput::Node { node_id: new_id, output_index };
|
||||||
@@ -550,6 +550,56 @@ impl NodeNetworkInterface {
|
|||||||
node_height
|
node_height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether every downstream path from the node's outputs stays within the dependent set defined by `classify`, meaning nothing else in the graph depends on this node.
|
||||||
|
/// Reaching an export or a dead end (a walked node with no outward wires) always escapes. O(nodes + wires) per call.
|
||||||
|
pub(crate) fn is_sole_dependent(&mut self, node_id: NodeId, network_path: &[NodeId], classify: impl Fn(NodeId, usize) -> SoleDependentStep) -> bool {
|
||||||
|
let mut visited = HashSet::new();
|
||||||
|
let mut stack = vec![node_id];
|
||||||
|
|
||||||
|
while let Some(current_node) = stack.pop() {
|
||||||
|
if !visited.insert(current_node) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let number_of_outputs = self.number_of_outputs(¤t_node, network_path);
|
||||||
|
let Some(outward_wires) = self.outward_wires(network_path) else {
|
||||||
|
log::error!("Could not get outward wires in is_sole_dependent");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Classify every downstream connection of this node, collecting the ones to keep walking through
|
||||||
|
let mut has_downstream_connections = false;
|
||||||
|
let mut nodes_to_walk_through = Vec::new();
|
||||||
|
for output_index in 0..number_of_outputs {
|
||||||
|
let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(current_node, output_index)) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for downstream_connection in downstream_connections {
|
||||||
|
has_downstream_connections = true;
|
||||||
|
let InputConnector::Node {
|
||||||
|
node_id: downstream_node,
|
||||||
|
input_index,
|
||||||
|
} = downstream_connection
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
match classify(*downstream_node, *input_index) {
|
||||||
|
SoleDependentStep::Terminate => {}
|
||||||
|
SoleDependentStep::Continue => nodes_to_walk_through.push(*downstream_node),
|
||||||
|
SoleDependentStep::Escape => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_downstream_connections {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.extend(nodes_to_walk_through);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
// All chain nodes and branches from the chain which are sole dependents of the layer
|
// All chain nodes and branches from the chain which are sole dependents of the layer
|
||||||
pub fn upstream_nodes_below_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> HashSet<NodeId> {
|
pub fn upstream_nodes_below_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> HashSet<NodeId> {
|
||||||
// Every upstream node below layer must be a sole dependent
|
// Every upstream node below layer must be a sole dependent
|
||||||
@@ -605,66 +655,16 @@ impl NodeNetworkInterface {
|
|||||||
.upstream_flow_back_from_nodes(vec![potential_upstream_node], network_path, FlowType::UpstreamFlow)
|
.upstream_flow_back_from_nodes(vec![potential_upstream_node], network_path, FlowType::UpstreamFlow)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
{
|
{
|
||||||
let number_of_outputs = self.number_of_outputs(&upstream_node_from_input, network_path);
|
// A path terminates at an already-verified sole dependent or at the left input to the chain
|
||||||
|
let is_sole_dependent = self.is_sole_dependent(upstream_node_from_input, network_path, |downstream_node, input_index| {
|
||||||
// A node is a sole dependent if all outputs are sole dependents, and there are no dead ends
|
if sole_dependents.contains(&downstream_node) || downstream_node == *node_id && input_index == 1 {
|
||||||
let mut all_outputs_are_sole_dependents = true;
|
SoleDependentStep::Terminate
|
||||||
let mut dead_ends = 0;
|
} else {
|
||||||
|
SoleDependentStep::Continue
|
||||||
for output_index in 0..number_of_outputs {
|
|
||||||
let downstream_connections = {
|
|
||||||
let Some(outward_wires) = self.outward_wires(network_path) else {
|
|
||||||
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
outward_wires.get(&OutputConnector::node(upstream_node_from_input, output_index)).cloned()
|
|
||||||
};
|
|
||||||
let Some(downstream_connections) = downstream_connections else {
|
|
||||||
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let mut current_output_is_sole_dependent = true;
|
|
||||||
let mut stack = downstream_connections;
|
|
||||||
while let Some(current_downstream_connection) = stack.pop() {
|
|
||||||
// Iterate downstream. If a sole dependent or chain_node_id is reached, then stop the iteration. If the exports is eventually reached, then it is not a sole dependent
|
|
||||||
match ¤t_downstream_connection {
|
|
||||||
InputConnector::Node {
|
|
||||||
node_id: downstream_node_id,
|
|
||||||
input_index,
|
|
||||||
} => {
|
|
||||||
// Stop iterating once the downstream node is the left input to the chain or a sole dependent
|
|
||||||
if !(sole_dependents.contains(downstream_node_id) || downstream_node_id == node_id && *input_index == 1) {
|
|
||||||
// Continue iterating downstream for the downstream node
|
|
||||||
let number_of_outputs = self.number_of_outputs(downstream_node_id, network_path);
|
|
||||||
let Some(outward_wires) = self.outward_wires(network_path) else {
|
|
||||||
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let mut has_downstream_connections = false;
|
|
||||||
for output_index in 0..number_of_outputs {
|
|
||||||
let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*downstream_node_id, output_index)) else {
|
|
||||||
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if !downstream_connections.is_empty() {
|
|
||||||
has_downstream_connections = true;
|
|
||||||
}
|
|
||||||
stack.extend(downstream_connections.clone());
|
|
||||||
}
|
|
||||||
if !has_downstream_connections {
|
|
||||||
dead_ends += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InputConnector::Export(_) => current_output_is_sole_dependent = false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !current_output_is_sole_dependent || dead_ends != 0 {
|
});
|
||||||
all_outputs_are_sole_dependents = false;
|
|
||||||
break;
|
if is_sole_dependent {
|
||||||
}
|
|
||||||
}
|
|
||||||
if all_outputs_are_sole_dependents && dead_ends == 0 {
|
|
||||||
sole_dependents.insert(upstream_node_from_input);
|
sole_dependents.insert(upstream_node_from_input);
|
||||||
} else {
|
} else {
|
||||||
upstream_chain_can_be_added = false;
|
upstream_chain_can_be_added = false;
|
||||||
|
|||||||
@@ -0,0 +1,307 @@
|
|||||||
|
use super::*;
|
||||||
|
use graph_craft::ProtoNodeIdentifier;
|
||||||
|
use graph_craft::concrete;
|
||||||
|
use graphene_std::Context;
|
||||||
|
use graphene_std::ContextDependencies;
|
||||||
|
|
||||||
|
// PartialEq required by message handlers
|
||||||
|
/// All persistent editor and Graphene data for a node, unified into a single flat shape.
|
||||||
|
/// Used to author node definitions, pass nodes through the editor, and serialize them for the clipboard.
|
||||||
|
///
|
||||||
|
/// [`Self::into_parts`] and [`Self::from_parts`] are the only places this is split into (or joined from) the
|
||||||
|
/// [`DocumentNode`] and [`DocumentNodePersistentMetadata`] halves stored by the network interface, so the parallel-tree
|
||||||
|
/// storage invariants hold by construction for every node built from a template.
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(from = "NodeTemplateParts", into = "NodeTemplateParts")]
|
||||||
|
pub struct NodeTemplate {
|
||||||
|
/// The graph data inputs to the node. Kept the same length as `input_metadata` by [`Self::normalize_input_metadata`] and [`Self::into_parts`].
|
||||||
|
pub inputs: Vec<NodeInput>,
|
||||||
|
/// Type of the argument which this node can be evaluated with.
|
||||||
|
pub call_argument: Type,
|
||||||
|
/// A nested network of templates, a proto node identifier, or the Extract tag.
|
||||||
|
pub implementation: NodeTemplateImplementation,
|
||||||
|
/// Represents the eye icon for hiding/showing the node in the graph UI.
|
||||||
|
pub visible: bool,
|
||||||
|
/// Prevents identical proto nodes from being deduplicated during compilation, e.g. for monitor nodes.
|
||||||
|
pub skip_deduplication: bool,
|
||||||
|
/// List of Extract and Inject annotations for the Context.
|
||||||
|
pub context_features: ContextDependencies,
|
||||||
|
/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the implementation name is displayed in italics.
|
||||||
|
pub display_name: String,
|
||||||
|
/// Metadata to override the properties panel widgets for each input. Kept the same length as `inputs`.
|
||||||
|
pub input_metadata: Vec<InputMetadata>,
|
||||||
|
pub output_names: Vec<String>,
|
||||||
|
/// Represents the lock icon for locking/unlocking the node in the graph UI.
|
||||||
|
pub locked: bool,
|
||||||
|
/// Indicates that the node will be shown in the Properties panel when it would otherwise be empty.
|
||||||
|
pub pinned: bool,
|
||||||
|
/// Whether the node is displayed as a left-to-right node or bottom-to-top layer, along with its position.
|
||||||
|
pub node_type_metadata: NodeTypePersistentMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NodeTemplate {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
inputs: Vec::new(),
|
||||||
|
call_argument: concrete!(Context),
|
||||||
|
implementation: NodeTemplateImplementation::default(),
|
||||||
|
visible: true,
|
||||||
|
skip_deduplication: false,
|
||||||
|
context_features: ContextDependencies::default(),
|
||||||
|
display_name: String::new(),
|
||||||
|
input_metadata: Vec::new(),
|
||||||
|
output_names: Vec::new(),
|
||||||
|
locked: false,
|
||||||
|
pinned: false,
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The implementation of a [`NodeTemplate`], mirroring [`DocumentNodeImplementation`] but carrying unified templates for nested network nodes.
|
||||||
|
// Templates are transient authoring objects, so the Network variant's size is not worth the authoring noise of boxing
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum NodeTemplateImplementation {
|
||||||
|
Network(NodeNetworkTemplate),
|
||||||
|
ProtoNode(ProtoNodeIdentifier),
|
||||||
|
Extract,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NodeTemplateImplementation {
|
||||||
|
fn default() -> Self {
|
||||||
|
NodeTemplateImplementation::ProtoNode(graphene_std::ops::passthrough::IDENTIFIER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A nested network within a [`NodeTemplate`], holding each nested node as a unified template alongside the network-level persistent metadata.
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct NodeNetworkTemplate {
|
||||||
|
pub exports: Vec<NodeInput>,
|
||||||
|
pub nodes: HashMap<NodeId, NodeTemplate>,
|
||||||
|
pub scope_injections: HashMap<String, (NodeId, Type)>,
|
||||||
|
/// The identifier of the [`DocumentNodeDefinition`] this network was instantiated from, if unmodified.
|
||||||
|
///
|
||||||
|
/// [`DocumentNodeDefinition`]: crate::messages::portfolio::document::node_graph::document_node_definitions::DocumentNodeDefinition
|
||||||
|
pub reference: Option<String>,
|
||||||
|
/// The display order of pinned nodes in the Properties panel.
|
||||||
|
pub pinned_node_order: Vec<NodeId>,
|
||||||
|
pub previewing: Previewing,
|
||||||
|
pub navigation_metadata: NavigationMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeTemplate {
|
||||||
|
/// Joins a [`DocumentNode`] and its persistent metadata into the unified template shape. Missing nested metadata is filled with defaults.
|
||||||
|
pub fn from_parts(document_node: DocumentNode, persistent_node_metadata: DocumentNodePersistentMetadata) -> Self {
|
||||||
|
let DocumentNode {
|
||||||
|
inputs,
|
||||||
|
call_argument,
|
||||||
|
implementation,
|
||||||
|
visible,
|
||||||
|
skip_deduplication,
|
||||||
|
context_features,
|
||||||
|
original_location: _,
|
||||||
|
} = document_node;
|
||||||
|
let DocumentNodePersistentMetadata {
|
||||||
|
display_name,
|
||||||
|
input_metadata,
|
||||||
|
output_names,
|
||||||
|
locked,
|
||||||
|
pinned,
|
||||||
|
node_type_metadata,
|
||||||
|
network_metadata,
|
||||||
|
} = persistent_node_metadata;
|
||||||
|
|
||||||
|
let implementation = match implementation {
|
||||||
|
DocumentNodeImplementation::Network(network) => {
|
||||||
|
let mut nested_persistent = network_metadata.map(|metadata| metadata.persistent_metadata).unwrap_or_default();
|
||||||
|
|
||||||
|
// Pair each nested node with its metadata by ID, recursively
|
||||||
|
let mut nodes = HashMap::with_capacity(network.nodes.len());
|
||||||
|
for (node_id, node) in network.nodes {
|
||||||
|
let node_persistent_metadata = nested_persistent.node_metadata.remove(&node_id).map(|metadata| metadata.persistent_metadata).unwrap_or_default();
|
||||||
|
nodes.insert(node_id, NodeTemplate::from_parts(node, node_persistent_metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeTemplateImplementation::Network(NodeNetworkTemplate {
|
||||||
|
exports: network.exports,
|
||||||
|
nodes,
|
||||||
|
scope_injections: network.scope_injections.into_iter().collect(),
|
||||||
|
reference: nested_persistent.reference,
|
||||||
|
pinned_node_order: nested_persistent.pinned_node_order,
|
||||||
|
previewing: nested_persistent.previewing,
|
||||||
|
navigation_metadata: nested_persistent.navigation_metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
DocumentNodeImplementation::ProtoNode(identifier) => NodeTemplateImplementation::ProtoNode(identifier),
|
||||||
|
DocumentNodeImplementation::Extract => NodeTemplateImplementation::Extract,
|
||||||
|
};
|
||||||
|
|
||||||
|
NodeTemplate {
|
||||||
|
inputs,
|
||||||
|
call_argument,
|
||||||
|
implementation,
|
||||||
|
visible,
|
||||||
|
skip_deduplication,
|
||||||
|
context_features,
|
||||||
|
display_name,
|
||||||
|
input_metadata,
|
||||||
|
output_names,
|
||||||
|
locked,
|
||||||
|
pinned,
|
||||||
|
node_type_metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Splits the template into the [`DocumentNode`] and [`DocumentNodePersistentMetadata`] halves stored by the network interface.
|
||||||
|
pub fn into_parts(self) -> (DocumentNode, DocumentNodePersistentMetadata) {
|
||||||
|
let NodeTemplate {
|
||||||
|
inputs,
|
||||||
|
call_argument,
|
||||||
|
implementation,
|
||||||
|
visible,
|
||||||
|
skip_deduplication,
|
||||||
|
context_features,
|
||||||
|
display_name,
|
||||||
|
mut input_metadata,
|
||||||
|
output_names,
|
||||||
|
locked,
|
||||||
|
pinned,
|
||||||
|
node_type_metadata,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let (implementation, network_metadata) = implementation.into_parts();
|
||||||
|
|
||||||
|
// The stored metadata invariant requires exactly one input metadata entry per input
|
||||||
|
input_metadata.resize_with(inputs.len(), InputMetadata::default);
|
||||||
|
|
||||||
|
let document_node = DocumentNode {
|
||||||
|
inputs,
|
||||||
|
call_argument,
|
||||||
|
implementation,
|
||||||
|
visible,
|
||||||
|
skip_deduplication,
|
||||||
|
context_features,
|
||||||
|
original_location: Default::default(),
|
||||||
|
};
|
||||||
|
let persistent_node_metadata = DocumentNodePersistentMetadata {
|
||||||
|
display_name,
|
||||||
|
input_metadata,
|
||||||
|
output_names,
|
||||||
|
locked,
|
||||||
|
pinned,
|
||||||
|
node_type_metadata,
|
||||||
|
network_metadata,
|
||||||
|
};
|
||||||
|
(document_node, persistent_node_metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`DocumentNode`] half alone, for callers performing raw network surgery.
|
||||||
|
pub fn into_document_node(self) -> DocumentNode {
|
||||||
|
self.into_parts().0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resizes `input_metadata` to match `inputs` at every nesting level, filling gaps with defaults.
|
||||||
|
pub fn normalize_input_metadata(&mut self) {
|
||||||
|
self.input_metadata.resize_with(self.inputs.len(), InputMetadata::default);
|
||||||
|
|
||||||
|
if let NodeTemplateImplementation::Network(network_template) = &mut self.implementation {
|
||||||
|
for nested_template in network_template.nodes.values_mut() {
|
||||||
|
nested_template.normalize_input_metadata();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeTemplateImplementation {
|
||||||
|
/// Splits into the [`DocumentNodeImplementation`] and the nested network metadata stored alongside it.
|
||||||
|
pub fn into_parts(self) -> (DocumentNodeImplementation, Option<NodeNetworkMetadata>) {
|
||||||
|
match self {
|
||||||
|
NodeTemplateImplementation::Network(network_template) => {
|
||||||
|
let NodeNetworkTemplate {
|
||||||
|
exports,
|
||||||
|
nodes,
|
||||||
|
scope_injections,
|
||||||
|
reference,
|
||||||
|
pinned_node_order,
|
||||||
|
previewing,
|
||||||
|
navigation_metadata,
|
||||||
|
} = network_template;
|
||||||
|
|
||||||
|
// Split each nested template into its two halves, recursively
|
||||||
|
let mut network = NodeNetwork {
|
||||||
|
exports,
|
||||||
|
scope_injections: scope_injections.into_iter().collect(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut node_metadata = HashMap::with_capacity(nodes.len());
|
||||||
|
for (node_id, node_template) in nodes {
|
||||||
|
let (document_node, persistent_metadata) = node_template.into_parts();
|
||||||
|
network.nodes.insert(node_id, document_node);
|
||||||
|
node_metadata.insert(
|
||||||
|
node_id,
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata,
|
||||||
|
transient_metadata: Default::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let network_metadata = NodeNetworkMetadata {
|
||||||
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
|
reference,
|
||||||
|
node_metadata,
|
||||||
|
pinned_node_order,
|
||||||
|
previewing,
|
||||||
|
navigation_metadata,
|
||||||
|
selection_undo_history: Default::default(),
|
||||||
|
selection_redo_history: Default::default(),
|
||||||
|
},
|
||||||
|
transient_metadata: Default::default(),
|
||||||
|
};
|
||||||
|
(DocumentNodeImplementation::Network(network), Some(network_metadata))
|
||||||
|
}
|
||||||
|
NodeTemplateImplementation::ProtoNode(identifier) => (DocumentNodeImplementation::ProtoNode(identifier), None),
|
||||||
|
NodeTemplateImplementation::Extract => (DocumentNodeImplementation::Extract, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects resource IDs referenced by a template and its nested networks.
|
||||||
|
pub fn collect_template_resources(template: &NodeTemplate, out: &mut HashSet<ResourceId>) {
|
||||||
|
for input in &template.inputs {
|
||||||
|
collect_input_resource(input, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let NodeTemplateImplementation::Network(network_template) = &template.implementation {
|
||||||
|
for export in &network_template.exports {
|
||||||
|
collect_input_resource(export, out);
|
||||||
|
}
|
||||||
|
for nested_template in network_template.nodes.values() {
|
||||||
|
collect_template_resources(nested_template, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The legacy two-tree shape of [`NodeTemplate`], kept as its serde representation so serialized clipboard data round-trips across versions.
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
struct NodeTemplateParts {
|
||||||
|
document_node: DocumentNode,
|
||||||
|
persistent_node_metadata: DocumentNodePersistentMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NodeTemplateParts> for NodeTemplate {
|
||||||
|
fn from(parts: NodeTemplateParts) -> Self {
|
||||||
|
NodeTemplate::from_parts(parts.document_node, parts.persistent_node_metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NodeTemplate> for NodeTemplateParts {
|
||||||
|
fn from(template: NodeTemplate) -> Self {
|
||||||
|
let (document_node, persistent_node_metadata) = template.into_parts();
|
||||||
|
NodeTemplateParts {
|
||||||
|
document_node,
|
||||||
|
persistent_node_metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -814,14 +814,6 @@ pub struct NavigationMetadata {
|
|||||||
pub node_graph_width: f64,
|
pub node_graph_width: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// PartialEq required by message handlers
|
|
||||||
/// All persistent editor and Graphene data for a node. Used to serialize and deserialize a node, pass it through the editor, and create definitions.
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct NodeTemplate {
|
|
||||||
pub document_node: DocumentNode,
|
|
||||||
pub persistent_node_metadata: DocumentNodePersistentMetadata,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum TransactionStatus {
|
pub enum TransactionStatus {
|
||||||
Started,
|
Started,
|
||||||
@@ -830,7 +822,21 @@ pub enum TransactionStatus {
|
|||||||
Finished,
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How [`NodeNetworkInterface::is_sole_dependent`] should treat a downstream connector it encounters.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) enum SoleDependentStep {
|
||||||
|
/// The downstream path ends here, inside the dependent set.
|
||||||
|
Terminate,
|
||||||
|
/// Keep walking downstream through this node.
|
||||||
|
Continue,
|
||||||
|
/// The path leaves the dependent set, so the candidate is not a sole dependent.
|
||||||
|
Escape,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn collect_network_resources(network: &NodeNetwork, out: &mut HashSet<ResourceId>) {
|
pub(crate) fn collect_network_resources(network: &NodeNetwork, out: &mut HashSet<ResourceId>) {
|
||||||
|
for export in &network.exports {
|
||||||
|
collect_input_resource(export, out);
|
||||||
|
}
|
||||||
for node in network.nodes.values() {
|
for node in network.nodes.values() {
|
||||||
collect_node_resources(node, out);
|
collect_node_resources(node, out);
|
||||||
}
|
}
|
||||||
@@ -839,13 +845,18 @@ pub(crate) fn collect_network_resources(network: &NodeNetwork, out: &mut HashSet
|
|||||||
/// Collects resource IDs referenced by a node and its nested networks.
|
/// Collects resource IDs referenced by a node and its nested networks.
|
||||||
pub fn collect_node_resources(node: &DocumentNode, out: &mut HashSet<ResourceId>) {
|
pub fn collect_node_resources(node: &DocumentNode, out: &mut HashSet<ResourceId>) {
|
||||||
for input in &node.inputs {
|
for input in &node.inputs {
|
||||||
if let NodeInput::Value { tagged_value, .. } = input
|
collect_input_resource(input, out);
|
||||||
&& let TaggedValue::Resource(id) = &**tagged_value
|
|
||||||
{
|
|
||||||
out.insert(*id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let DocumentNodeImplementation::Network(nested) = &node.implementation {
|
if let DocumentNodeImplementation::Network(nested) = &node.implementation {
|
||||||
collect_network_resources(nested, out);
|
collect_network_resources(nested, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Records the resource ID held by a value input, covering node inputs and export slots alike.
|
||||||
|
pub(crate) fn collect_input_resource(input: &NodeInput, out: &mut HashSet<ResourceId>) {
|
||||||
|
if let NodeInput::Value { tagged_value, .. } = input
|
||||||
|
&& let TaggedValue::Resource(id) = &**tagged_value
|
||||||
|
{
|
||||||
|
out.insert(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -418,9 +418,6 @@ impl<'a, 'p> NetworkView<'a, 'p> {
|
|||||||
let node = self.node(node_id)?;
|
let node = self.node(node_id)?;
|
||||||
let node_metadata = self.node_metadata(node_id)?;
|
let node_metadata = self.node_metadata(node_id)?;
|
||||||
|
|
||||||
Ok(NodeTemplate {
|
Ok(NodeTemplate::from_parts(node.clone(), node_metadata.persistent_metadata.clone()))
|
||||||
persistent_node_metadata: node_metadata.persistent_metadata.clone(),
|
|
||||||
document_node: node.clone(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type};
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type};
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, NodeTemplateImplementation, OutputConnector};
|
||||||
use crate::messages::prelude::DocumentMessageHandler;
|
use crate::messages::prelude::DocumentMessageHandler;
|
||||||
use glam::{DVec2, IVec2};
|
use glam::{DVec2, IVec2};
|
||||||
use graph_craft::application_io::resource::{DataSource, Resource, ResourceHash, ResourceId};
|
use graph_craft::application_io::resource::{DataSource, Resource, ResourceHash, ResourceId};
|
||||||
@@ -1001,9 +1001,9 @@ pub fn document_migration_string_preprocessing(document_serialized_content: Stri
|
|||||||
/// so the staged input-count migrations can still upgrade old text nodes before the split.
|
/// so the staged input-count migrations can still upgrade old text nodes before the split.
|
||||||
fn legacy_text_node_template() -> Option<NodeTemplate> {
|
fn legacy_text_node_template() -> Option<NodeTemplate> {
|
||||||
let mut template = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?.default_node_template();
|
let mut template = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?.default_node_template();
|
||||||
template.document_node.implementation = DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::text::TextNode"));
|
template.implementation = NodeTemplateImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::text::TextNode"));
|
||||||
template.document_node.inputs.push(NodeInput::value(TaggedValue::Bool(false), false));
|
template.inputs.push(NodeInput::value(TaggedValue::Bool(false), false));
|
||||||
template.persistent_node_metadata.input_metadata.push(Default::default());
|
template.input_metadata.push(Default::default());
|
||||||
Some(template)
|
Some(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1158,10 +1158,12 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||||||
if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {
|
if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {
|
||||||
let node_path_without_type_args = protonode_id.as_str().split('<').next();
|
let node_path_without_type_args = protonode_id.as_str().split('<').next();
|
||||||
if let Some(new) = node_path_without_type_args.and_then(|node_path| replacements.get(node_path)) {
|
if let Some(new) = node_path_without_type_args.and_then(|node_path| replacements.get(node_path)) {
|
||||||
let mut default_template = NodeTemplate::default();
|
let mut default_template = NodeTemplate {
|
||||||
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone());
|
implementation: NodeTemplateImplementation::ProtoNode(new.clone()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
|
document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
|
||||||
document.network_interface.set_call_argument(node_id, &network_path, default_template.document_node.call_argument);
|
document.network_interface.set_call_argument(node_id, &network_path, default_template.call_argument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1231,7 +1233,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||||||
// (which represented radians in the legacy format) reaches the now-degrees Rotation input correctly.
|
// (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)) {
|
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();
|
let mut multiply_template = multiply_node.default_node_template();
|
||||||
multiply_template.document_node.inputs[1] = NodeInput::value(TaggedValue::F64(180. / PI), false);
|
multiply_template.inputs[1] = NodeInput::value(TaggedValue::F64(180. / PI), false);
|
||||||
let multiply_node_id = NodeId::new();
|
let multiply_node_id = NodeId::new();
|
||||||
if let Some(transform_position) = document.network_interface.position_from_downstream_node(node_id, network_path) {
|
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);
|
let multiply_position = transform_position + IVec2::new(-7, 1);
|
||||||
@@ -2219,7 +2221,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
// A brush node saved before `Item<Raster<CPU>>` had a default stored its unconnected background as the invalid `()`,
|
// A brush node saved before `Item<Raster<CPU>>` had a default stored its unconnected background as the invalid `()`,
|
||||||
// which fails type resolution against the raster primary; adopt the definition's empty-raster default instead.
|
// which fails type resolution against the raster primary; adopt the definition's empty-raster default instead.
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && matches!(node.inputs.first().and_then(|input| input.as_value()), Some(TaggedValue::None)) {
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && matches!(node.inputs.first().and_then(|input| input.as_value()), Some(TaggedValue::None)) {
|
||||||
let default_background = resolve_document_node_type(&reference)?.node_template.document_node.inputs.first()?.clone();
|
let default_background = resolve_document_node_type(&reference)?.node_template.inputs.first()?.clone();
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), default_background, network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 0), default_background, network_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2470,7 +2472,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let mut subtract_template = subtract_def.default_node_template();
|
let mut subtract_template = subtract_def.default_node_template();
|
||||||
subtract_template.document_node.inputs[1] = NodeInput::value(TaggedValue::F64(1.), false);
|
subtract_template.inputs[1] = NodeInput::value(TaggedValue::F64(1.), false);
|
||||||
let subtract_id = NodeId::new();
|
let subtract_id = NodeId::new();
|
||||||
|
|
||||||
// Create Divide node: old_progression / (N-1) → new progression
|
// Create Divide node: old_progression / (N-1) → new progression
|
||||||
@@ -2565,7 +2567,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let mut transform_template = transform_node_type.default_node_template();
|
let mut transform_template = transform_node_type.default_node_template();
|
||||||
transform_template.document_node.inputs[1] = NodeInput::value(TaggedValue::DVec2(start), false);
|
transform_template.inputs[1] = NodeInput::value(TaggedValue::DVec2(start), false);
|
||||||
|
|
||||||
let transform_id = NodeId::new();
|
let transform_id = NodeId::new();
|
||||||
|
|
||||||
@@ -2626,7 +2628,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let mut transform_template = transform_node_type.default_node_template();
|
let mut transform_template = transform_node_type.default_node_template();
|
||||||
transform_template.document_node.inputs[1] = NodeInput::value(TaggedValue::DVec2(start), false);
|
transform_template.inputs[1] = NodeInput::value(TaggedValue::DVec2(start), false);
|
||||||
|
|
||||||
let transform_id = NodeId::new();
|
let transform_id = NodeId::new();
|
||||||
|
|
||||||
@@ -2711,7 +2713,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
// A value input stored as a List-form TypeDefault adopts the definition's current default when the connector's declared default has since changed (e.g. the connector was ranked down to Item).
|
// A value input stored as a List-form TypeDefault adopts the definition's current default when the connector's declared default has since changed (e.g. the connector was ranked down to Item).
|
||||||
// The red-slash no-paint choice shares that stored form but is a deliberate value, not a stale disconnect default, so it is exempt.
|
// The red-slash no-paint choice shares that stored form but is a deliberate value, not a stale disconnect default, so it is exempt.
|
||||||
if let Some(definition) = resolve_document_node_type(&reference) {
|
if let Some(definition) = resolve_document_node_type(&reference) {
|
||||||
let definition_inputs = definition.node_template.document_node.inputs.clone();
|
let definition_inputs = definition.node_template.inputs.clone();
|
||||||
for (index, definition_input) in definition_inputs.iter().enumerate() {
|
for (index, definition_input) in definition_inputs.iter().enumerate() {
|
||||||
if !matches!(definition_input, NodeInput::Value { .. }) {
|
if !matches!(definition_input, NodeInput::Value { .. }) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user