Add colors to all nodes in a graph, even if disconnected, and properly display hidden network imports (#1921)

* Get output/input types by iterating to proto node. Fix types when undoing/redoing

* Remove unused code

* Fix types not updating when modified

* Improve code quality

* Improve proto node type lookup

* Nits

* Fix crash when adding Extract

---------

Co-authored-by: dennis@kobert.dev <dennis@kobert.dev>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant 2024-08-10 15:27:15 -07:00 committed by GitHub
parent 1f278799d6
commit 60707c0369
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 203 additions and 152 deletions

View File

@ -1375,6 +1375,7 @@ impl DocumentMessageHandler {
// Set the previous network navigation metadata to the current navigation metadata // Set the previous network navigation metadata to the current navigation metadata
network_interface.copy_all_navigation_metadata(&self.network_interface); network_interface.copy_all_navigation_metadata(&self.network_interface);
std::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types);
//Update the metadata transform based on document PTZ //Update the metadata transform based on document PTZ
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);

View File

@ -11,10 +11,9 @@ use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graphene_core::*; use graphene_core::*;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
use renderer::{ClickTarget, Quad}; use renderer::{ClickTarget, Quad};
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
@ -557,7 +556,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::DisconnectInput { responses.add(NodeGraphMessage::DisconnectInput {
input_connector: disconnecting.clone(), input_connector: disconnecting.clone(),
}); });
// Update the front end that the node is disconnected // Update the frontend that the node is disconnected
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
self.disconnecting = None; self.disconnecting = None;
@ -927,7 +926,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(PropertiesPanelMessage::Refresh); responses.add(PropertiesPanelMessage::Refresh);
} }
NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets {
click_targets: Some(network_interface.collect_front_end_click_targets(breadcrumb_network_path)), click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
}), }),
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }), NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),
NodeGraphMessage::SendGraph => { NodeGraphMessage::SendGraph => {
@ -1578,14 +1577,9 @@ impl NodeGraphMessageHandler {
let frontend_graph_inputs = node.inputs.iter().enumerate().map(|(index, _)| { let frontend_graph_inputs = node.inputs.iter().enumerate().map(|(index, _)| {
// Convert the index in all inputs to the index in only the exposed inputs // Convert the index in all inputs to the index in only the exposed inputs
// TODO: Only display input type if potential inputs in node_registry are all the same type // TODO: Only display input type if potential inputs in node_registry are all the same type
let node_types = network_interface.resolved_types.types.get(node_id_path.as_slice()); let node_type = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path);
// TODO: Should display the color of the "most commonly relevant" (we'd need some sort of precedence) data type it allows given the current generic form that's constrained by the other present connections. // TODO: Should display the color of the "most commonly relevant" (we'd need some sort of precedence) data type it allows given the current generic form that's constrained by the other present connections.
let frontend_data_type = if let Some(node_types) = node_types { let data_type = FrontendGraphDataType::with_type(&node_type);
FrontendGraphDataType::with_type(&node_types.inputs[index])
} else {
FrontendGraphDataType::General
};
let input_name = node_metadata let input_name = node_metadata
.persistent_metadata .persistent_metadata
@ -1595,9 +1589,9 @@ impl NodeGraphMessageHandler {
.unwrap_or(network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path).nested_type().to_string()); .unwrap_or(network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path).nested_type().to_string());
FrontendGraphInput { FrontendGraphInput {
data_type: frontend_data_type, data_type,
name: input_name, name: input_name,
resolved_type: node_types.map(|types| format!("{:?}", types.inputs[index])), resolved_type: Some(format!("{:?}", node_type)),
connected_to: None, connected_to: None,
} }
}); });
@ -1628,7 +1622,7 @@ impl NodeGraphMessageHandler {
.map(|(_, input_type)| input_type) .map(|(_, input_type)| input_type)
.collect(); .collect();
let output_types = Self::get_output_types(node, &network_interface.resolved_types, node_id_path); let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
let primary_output_type = output_types.first().expect("Primary output should always exist"); let primary_output_type = output_types.first().expect("Primary output should always exist");
let frontend_data_type = if let Some(output_type) = primary_output_type { let frontend_data_type = if let Some(output_type) = primary_output_type {
FrontendGraphDataType::with_type(output_type) FrontendGraphDataType::with_type(output_type)
@ -1806,83 +1800,6 @@ impl NodeGraphMessageHandler {
} }
} }
/// Retrieves the output types for a given document node and its exports.
///
/// This function traverses the node and its nested network structure (if applicable) to determine
/// the types of all outputs, including the primary output and any additional exports.
///
/// # Arguments
///
/// * `node` - A reference to the `DocumentNode` for which to determine output types.
/// * `resolved_types` - A reference to `ResolvedDocumentNodeTypes` containing pre-resolved type information.
/// * `node_id_path` - A slice of `NodeId`s representing the path to the current node in the document graph.
///
/// # Returns
///
/// A `Vec<Option<Type>>` where:
/// - The first element is the primary output type of the node.
/// - Subsequent elements are types of additional exports (if the node is a network).
/// - `None` values indicate that a type couldn't be resolved for a particular output.
///
/// # Behavior
///
/// 1. Retrieves the primary output type from `resolved_types`.
/// 2. If the node is a network:
/// - Iterates through its exports (skipping the first/primary export).
/// - For each export, traverses the network until reaching a protonode or terminal condition.
/// - Determines the output type based on the final node/value encountered.
/// 3. Collects and returns all resolved types.
///
/// # Note
///
/// This function assumes that export indices and node IDs always exist within their respective
/// collections. It will panic if these assumptions are violated.
pub fn get_output_types(node: &DocumentNode, resolved_types: &ResolvedDocumentNodeTypes, node_id_path: &[NodeId]) -> Vec<Option<Type>> {
let mut output_types = Vec::new();
let primary_output_type = resolved_types.types.get(node_id_path).map(|ty| ty.output.clone());
// If the node is not a protonode, get types by traversing across exports until a proto node is reached.
if let graph_craft::document::DocumentNodeImplementation::Network(internal_network) = &node.implementation {
for export in internal_network.exports.iter() {
let mut current_export = export;
let mut current_network = internal_network;
let mut current_path = node_id_path.to_owned();
while let NodeInput::Node { node_id, output_index, .. } = current_export {
current_path.push(*node_id);
let next_node = current_network.nodes.get(node_id).expect("Export node id should always exist");
if let graph_craft::document::DocumentNodeImplementation::Network(next_network) = &next_node.implementation {
current_network = next_network;
current_export = next_network.exports.get(*output_index).expect("Export at output index should always exist");
} else {
break;
}
}
let output_type: Option<Type> = match current_export {
NodeInput::Node { output_index, .. } => {
// Current export is pointing to a proto node where type can be derived
assert_eq!(*output_index, 0, "Output index for a proto node should always be 0");
resolved_types.types.get(&current_path).map(|ty| ty.output.clone())
}
NodeInput::Value { tagged_value, .. } => Some(tagged_value.ty()),
NodeInput::Network { import_type, .. } => Some(import_type.clone()),
_ => None,
};
output_types.push(output_type);
}
} else {
if primary_output_type.is_none() {
log::warn!("no output type found for {:?} {:?}", node_id_path, &node.implementation);
}
output_types.push(primary_output_type);
}
output_types
}
fn build_wire_path_string(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> String { fn build_wire_path_string(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> String {
let locations = Self::build_wire_path_locations(output_position, input_position, vertical_out, vertical_in); let locations = Self::build_wire_path_locations(output_position, input_position, vertical_out, vertical_in);
let smoothing = 0.5; let smoothing = 0.5;

View File

@ -3,7 +3,6 @@ use super::misc::PTZ;
use super::nodes::SelectedNodes; use super::nodes::SelectedNodes;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::utility_types::{FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; use crate::messages::portfolio::document::node_graph::utility_types::{FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput};
use crate::messages::prelude::NodeGraphMessageHandler;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
@ -378,20 +377,54 @@ impl NodeNetworkInterface {
/// Get the [`Type`] for any InputConnector /// Get the [`Type`] for any InputConnector
pub fn input_type(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Type { pub fn input_type(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Type {
let Some(network) = self.network(network_path) else { // TODO: If the input_connector is a NodeInput::Value, return the type of the tagged value
log::error!("Could not get network in input_type");
return concrete!(());
};
// TODO: Store types for all document nodes, not just the compiled proto nodes, which currently skips isolated nodes // TODO: Store types for all document nodes, not just the compiled proto nodes, which currently skips isolated nodes
let node_type_from_compiled_network = if let Some(node_id) = input_connector.node_id() { let node_type_from_compiled_network = if let Some(node_id) = input_connector.node_id() {
let Some(current_network) = self.network(network_path) else {
log::error!("Could not get current network in input_type");
return concrete!(());
};
let Some(node) = current_network.nodes.get(&node_id) else {
log::error!("Could not get node {node_id} in input_type");
return concrete!(());
};
let node_id_path = [network_path, &[node_id]].concat().clone(); let node_id_path = [network_path, &[node_id]].concat().clone();
self.resolved_types match &node.implementation {
.types DocumentNodeImplementation::Network(nested_network) => {
.get(node_id_path.as_slice()) let downstream_connection = nested_network
.map(|node_types| node_types.inputs[input_connector.input_index()].clone()) .nodes
} else if let Some(encapsulating_node) = self.encapsulating_node(network_path) { .iter()
let output_types = NodeGraphMessageHandler::get_output_types(encapsulating_node, &self.resolved_types, network_path); .flat_map(|(node_id, node)| node.inputs.iter().enumerate().map(|(input_index, input)| (InputConnector::node(*node_id, input_index), input)))
.chain(nested_network.exports.iter().enumerate().map(|(export_index, export)| (InputConnector::Export(export_index), export)))
.find(|(_, input)| {
if let NodeInput::Network { import_index, .. } = input {
*import_index == input_connector.input_index()
} else {
false
}
});
if let Some((input_connector, _)) = downstream_connection {
Some(self.input_type(&input_connector, &node_id_path))
}
// Nothing is connected to the import
else {
Some(concrete!(()))
}
}
DocumentNodeImplementation::ProtoNode(_) => {
// If a node has manual composition, then offset the input index by 1 since the proto node also includes the type of the parameter passed through manual composition.
let manual_composition_offset = if node.manual_composition.is_some() { 1 } else { 0 };
self.resolved_types
.types
.get(node_id_path.as_slice())
.map(|node_types| node_types.inputs[input_connector.input_index() + manual_composition_offset].clone())
}
DocumentNodeImplementation::Extract => Some(concrete!(())),
}
} else if let Some(encapsulating_node_id) = network_path.last() {
let mut encapsulating_node_id_path = network_path.to_vec();
encapsulating_node_id_path.pop();
let output_types: Vec<Option<Type>> = self.output_types(encapsulating_node_id, &encapsulating_node_id_path);
output_types.get(input_connector.input_index()).map_or_else( output_types.get(input_connector.input_index()).map_or_else(
|| { || {
warn!("Could not find output type for export node"); warn!("Could not find output type for export node");
@ -406,7 +439,10 @@ impl NodeNetworkInterface {
node_type_from_compiled_network.unwrap_or_else(|| { node_type_from_compiled_network.unwrap_or_else(|| {
// TODO: Once there is type inference (#1621), replace this workaround approach when disconnecting node inputs with NodeInput::Node(ToDefaultNode), // TODO: Once there is type inference (#1621), replace this workaround approach when disconnecting node inputs with NodeInput::Node(ToDefaultNode),
// TODO: which would be a new node that implements the Default trait (i.e. `Default::default()`) // TODO: which would be a new node that implements the Default trait (i.e. `Default::default()`)
let Some(network) = self.network(network_path) else {
log::error!("Could not get network in input_type");
return concrete!(());
};
// Resolve types from proto nodes in node_registry // Resolve types from proto nodes in node_registry
let Some(node_id) = input_connector.node_id() else { let Some(node_id) = input_connector.node_id() else {
return concrete!(()); return concrete!(());
@ -418,21 +454,7 @@ impl NodeNetworkInterface {
fn type_from_node(node: &DocumentNode, input_index: usize) -> Type { fn type_from_node(node: &DocumentNode, input_index: usize) -> Type {
match &node.implementation { match &node.implementation {
DocumentNodeImplementation::ProtoNode(protonode) => { DocumentNodeImplementation::ProtoNode(protonode) => {
let Some(node_io_hashmap) = NODE_REGISTRY.get(protonode) else { let Some(node_types) = proto_node_type(protonode) else { return concrete!(()) };
log::error!("Could not get hashmap for proto node: {protonode:?}");
return concrete!(());
};
let mut all_node_io_types = node_io_hashmap.keys().collect::<Vec<_>>();
all_node_io_types.sort_by_key(|node_io_types| {
let mut hasher = DefaultHasher::new();
node_io_types.hash(&mut hasher);
hasher.finish()
});
let Some(node_types) = all_node_io_types.first() else {
log::error!("Could not get node_types from hashmap");
return concrete!(());
};
let skip_footprint = if node.manual_composition.is_some() { 1 } else { 0 }; let skip_footprint = if node.manual_composition.is_some() { 1 } else { 0 };
@ -464,6 +486,96 @@ impl NodeNetworkInterface {
}) })
} }
/// Retrieves the output types for a given document node and its exports.
///
/// This function traverses the node and its nested network structure (if applicable) to determine
/// the types of all outputs, including the primary output and any additional exports.
///
/// # Arguments
///
/// * `node` - A reference to the `DocumentNode` for which to determine output types.
/// * `resolved_types` - A reference to `ResolvedDocumentNodeTypes` containing pre-resolved type information.
/// * `node_id_path` - A slice of `NodeId`s representing the path to the current node in the document graph.
///
/// # Returns
///
/// A `Vec<Option<Type>>` where:
/// - The first element is the primary output type of the node.
/// - Subsequent elements are types of additional exports (if the node is a network).
/// - `None` values indicate that a type couldn't be resolved for a particular output.
///
/// # Behavior
///
/// 1. Retrieves the primary output type from `resolved_types`.
/// 2. If the node is a network:
/// - Iterates through its exports (skipping the first/primary export).
/// - For each export, traverses the network until reaching a protonode or terminal condition.
/// - Determines the output type based on the final node/value encountered.
/// 3. Collects and returns all resolved types.
///
/// # Note
///
/// This function assumes that export indices and node IDs always exist within their respective
/// collections. It will panic if these assumptions are violated.
pub fn output_types(&self, node_id: &NodeId, network_path: &[NodeId]) -> Vec<Option<Type>> {
let Some(network) = self.network(network_path) else {
log::error!("Could not get network in output_types");
return Vec::new();
};
let Some(node) = network.nodes.get(node_id) else {
log::error!("Could not get node {node_id} in output_types");
return Vec::new();
};
let mut output_types = Vec::new();
// If the node is not a protonode, get types by traversing across exports until a proto node is reached.
match &node.implementation {
graph_craft::document::DocumentNodeImplementation::Network(internal_network) => {
for export in internal_network.exports.iter() {
match export {
NodeInput::Node {
node_id: nested_node_id,
output_index,
..
} => {
let nested_output_types = self.output_types(nested_node_id, &[network_path, &[*node_id]].concat());
let Some(nested_nodes_output_types) = nested_output_types.get(*output_index) else {
log::error!("Could not get nested nodes output in output_types");
return Vec::new();
};
output_types.push(nested_nodes_output_types.clone());
}
NodeInput::Value { tagged_value, .. } => {
output_types.push(Some(tagged_value.ty()));
}
NodeInput::Network { .. } => {
// https://github.com/GraphiteEditor/Graphite/issues/1762
log::error!("Network input type cannot be connected to export");
return Vec::new();
}
NodeInput::Scope(_) => todo!(),
NodeInput::Inline(_) => todo!(),
}
}
}
graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode) => {
let node_id_path = &[network_path, &[*node_id]].concat();
let primary_output_type = self.resolved_types.types.get(node_id_path).map(|ty| ty.output.clone()).or_else(|| {
let node_types = proto_node_type(protonode)?;
Some(node_types.output.clone())
});
output_types.push(primary_output_type);
}
graph_craft::document::DocumentNodeImplementation::Extract => {
output_types.push(Some(concrete!(())));
}
}
output_types
}
pub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<IVec2> { pub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<IVec2> {
let top_left_position = self let top_left_position = self
.node_click_targets(node_id, network_path) .node_click_targets(node_id, network_path)
@ -501,19 +613,14 @@ impl NodeNetworkInterface {
if !network_path.is_empty() { if !network_path.is_empty() {
// TODO: https://github.com/GraphiteEditor/Graphite/issues/1767 // TODO: https://github.com/GraphiteEditor/Graphite/issues/1767
// TODO: Non exposed inputs are not added to the inputs_source_map, fix `pub fn document_node_types(&self) -> ResolvedDocumentNodeTypes` // TODO: Non exposed inputs are not added to the inputs_source_map, fix `pub fn document_node_types(&self) -> ResolvedDocumentNodeTypes`
let input_type = self.resolved_types.types.get(network_path).map(|nt| nt.inputs[*import_index].clone()); let mut encapsulating_path = network_path.to_vec();
let encapsulating_node_id = encapsulating_path.pop().unwrap();
let frontend_data_type = if let Some(input_type) = input_type.clone() { let input_type = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path);
FrontendGraphDataType::with_type(&input_type) let data_type = FrontendGraphDataType::with_type(&input_type);
} else {
FrontendGraphDataType::General
};
let import_name = if import_name.is_empty() { let import_name = if import_name.is_empty() {
input_type TaggedValue::from_type(&input_type).ty().to_string()
.clone()
.map(|input_type| TaggedValue::from_type(&input_type).ty().to_string())
.unwrap_or(format!("Import {}", import_index + 1))
} else { } else {
import_name import_name
}; };
@ -529,9 +636,9 @@ impl NodeNetworkInterface {
import_metadata = Some(( import_metadata = Some((
FrontendGraphOutput { FrontendGraphOutput {
data_type: frontend_data_type, data_type,
name: import_name, name: import_name,
resolved_type: input_type.map(|input| format!("{input:?}")), resolved_type: Some(format!("{input_type:?}")),
connected_to, connected_to,
}, },
click_target, click_target,
@ -561,9 +668,7 @@ impl NodeNetworkInterface {
}; };
let (frontend_data_type, input_type) = if let NodeInput::Node { node_id, output_index, .. } = export { let (frontend_data_type, input_type) = if let NodeInput::Node { node_id, output_index, .. } = export {
let node = network.nodes.get(node_id).expect("Node should always exist"); let output_types = self.output_types(node_id, network_path);
let node_id_path = &[network_path, &[*node_id]].concat();
let output_types = NodeGraphMessageHandler::get_output_types(node, &self.resolved_types, node_id_path);
if let Some(output_type) = output_types.get(*output_index).cloned().flatten() { if let Some(output_type) = output_types.get(*output_index).cloned().flatten() {
(FrontendGraphDataType::with_type(&output_type), Some(output_type.clone())) (FrontendGraphDataType::with_type(&output_type), Some(output_type.clone()))
@ -1050,6 +1155,24 @@ impl NodeNetworkInterface {
} }
} }
fn proto_node_type(protonode: &graph_craft::ProtoNodeIdentifier) -> Option<&graphene_std::NodeIOTypes> {
let Some(node_io_hashmap) = NODE_REGISTRY.get(protonode) else {
log::error!("Could not get hashmap for proto node: {protonode:?}");
return None;
};
let node_types = node_io_hashmap.keys().min_by_key(|node_io_types| {
let mut hasher = DefaultHasher::new();
node_io_types.hash(&mut hasher);
hasher.finish()
});
if node_types.is_none() {
log::error!("Could not get node_types from hashmap");
};
node_types
}
// Private mutable getters for use within the network interface // Private mutable getters for use within the network interface
impl NodeNetworkInterface { impl NodeNetworkInterface {
fn network_mut(&mut self, network_path: &[NodeId]) -> Option<&mut NodeNetwork> { fn network_mut(&mut self, network_path: &[NodeId]) -> Option<&mut NodeNetwork> {
@ -1670,12 +1793,12 @@ impl NodeNetworkInterface {
all_selected_nodes all_selected_nodes
} }
pub fn collect_front_end_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets { pub fn collect_frontend_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets {
let mut all_node_click_targets = Vec::new(); let mut all_node_click_targets = Vec::new();
let mut port_click_targets = Vec::new(); let mut port_click_targets = Vec::new();
let mut visibility_click_targets = Vec::new(); let mut visibility_click_targets = Vec::new();
let Some(network_metadata) = self.network_metadata(network_path) else { let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in collect_front_end_click_targets"); log::error!("Could not get nested network_metadata in collect_frontend_click_targets");
return FrontendClickTargets::default(); return FrontendClickTargets::default();
}; };
network_metadata.persistent_metadata.node_metadata.keys().copied().collect::<Vec<_>>().into_iter().for_each(|node_id| { network_metadata.persistent_metadata.node_metadata.keys().copied().collect::<Vec<_>>().into_iter().for_each(|node_id| {

View File

@ -26,10 +26,13 @@ const ImportsToVec2Array = Transform(({ obj }) => {
const ExportsToVec2Array = Transform(({ obj }) => { const ExportsToVec2Array = Transform(({ obj }) => {
const exports: { inputMetadata: FrontendGraphInput; position: XY }[] = []; const exports: { inputMetadata: FrontendGraphInput; position: XY }[] = [];
obj.exports.forEach(([inputMetadata, x, y]: [FrontendGraphInput, number, number]) => { obj.exports.forEach(([inputMetadata, x, y]: [FrontendGraphInput, number, number]) => {
inputMetadata.connectedTo = ((connectedTo: any) => { if (inputMetadata.connectedTo !== undefined) {
if (connectedTo?.import !== undefined) return { index: connectedTo?.import.index }; if (inputMetadata.connectedTo?.import !== undefined) {
return { nodeId: connectedTo?.node.nodeId, index: connectedTo?.node.outputIndex }; inputMetadata.connectedTo = { index: inputMetadata.connectedTo?.import.index };
})(inputMetadata.connectedTo); } else {
inputMetadata.connectedTo = { nodeId: inputMetadata.connectedTo?.node.nodeId, index: inputMetadata.connectedTo?.node.outputIndex };
}
}
exports.push({ inputMetadata, position: { x, y } }); exports.push({ inputMetadata, position: { x, y } });
}); });
return exports; return exports;
@ -200,6 +203,9 @@ export type OutputConnector = Node | Import;
export type InputConnector = Node | Export; export type InputConnector = Node | Export;
const CreateOutputConnectorOptional = Transform(({ obj }) => { const CreateOutputConnectorOptional = Transform(({ obj }) => {
if (obj.connectedTo == undefined) {
return undefined;
}
if (obj.connectedTo?.export !== undefined) { if (obj.connectedTo?.export !== undefined) {
return { index: obj.connectedTo?.export }; return { index: obj.connectedTo?.export };
} else if (obj.connectedTo?.import !== undefined) { } else if (obj.connectedTo?.import !== undefined) {
@ -221,7 +227,7 @@ export class FrontendGraphInput {
readonly resolvedType!: string | undefined; readonly resolvedType!: string | undefined;
@CreateOutputConnectorOptional @CreateOutputConnectorOptional
readonly connectedTo!: OutputConnector | undefined; connectedTo!: OutputConnector | undefined;
} }
const CreateInputConnectorArray = Transform(({ obj }) => { const CreateInputConnectorArray = Transform(({ obj }) => {

View File

@ -140,6 +140,12 @@ pub enum Type {
Future(Box<Type>), Future(Box<Type>),
} }
impl Default for Type {
fn default() -> Self {
concrete!(())
}
}
unsafe impl StaticType for Type { unsafe impl StaticType for Type {
type Static = Self; type Static = Self;
} }

View File

@ -8,7 +8,6 @@ use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContaine
use graph_craft::proto::{GraphErrorType, GraphErrors}; use graph_craft::proto::{GraphErrorType, GraphErrors};
use graph_craft::Type; use graph_craft::Type;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::error::Error; use std::error::Error;
use std::panic::UnwindSafe; use std::panic::UnwindSafe;
@ -36,7 +35,7 @@ impl Default for DynamicExecutor {
} }
} }
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeTypes { pub struct NodeTypes {
pub inputs: Vec<Type>, pub inputs: Vec<Type>,
@ -331,24 +330,23 @@ impl BorrowTree {
let node_path = &proto_node.original_location.path.as_ref().unwrap_or(const { &vec![] }); let node_path = &proto_node.original_location.path.as_ref().unwrap_or(const { &vec![] });
let entry = self.source_map.entry(node_path.to_vec().into()); let entry = self.source_map.entry(node_path.to_vec().into()).or_default();
let newly_inserted = matches!(entry, Entry::Vacant(_));
let entry = entry.or_insert(( let update = (
id, id,
NodeTypes { NodeTypes {
inputs, inputs,
output: node_io.output.clone(), output: node_io.output.clone(),
}, },
)); );
let modified = *entry != update;
entry.0 = id; *entry = update;
entry.1.output = node_io.output.clone(); modified
newly_inserted
} }
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`. /// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
/// ///
/// This method creates a new node contianer based on the provided `ProtoNode`, updates the source map, /// This method creates a new node container based on the provided `ProtoNode`, updates the source map,
/// and stores the node container in the `BorrowTree`. /// and stores the node container in the `BorrowTree`.
/// ///
/// ///