diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 01b5a44ec9..04536890ea 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -161,8 +161,8 @@ impl Dispatcher { Message::EndIntrospectionQueue => { self.queueing_introspection_messages = false; } - Message::ProcessIntrospectionQueue(introspected_inputs) => { - let update_message = PortfolioMessage::ProcessIntrospectionResponse { introspected_inputs }.into(); + Message::ProcessIntrospectionQueue(introspection_response) => { + let update_message = PortfolioMessage::ProcessIntrospectionResponse { introspection_response }.into(); // Update the state with the render output and introspected inputs Self::schedule_execution(&mut self.message_queues, true, [update_message]); diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index d687c3a1bc..e57fcf73c8 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -455,73 +455,73 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Creates a new Artboard which can be used as a working surface."), properties: None, }, - DocumentNodeDefinition { - identifier: "Load Image", - category: "Web Request", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], - manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::String("graphite:null".to_string()), false)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("Empty", "TODO").into(), ("URL", "TODO").into()], - output_names: vec!["Image".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Load Resource".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Decode Image".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("Loads an image from a given URL"), - properties: None, - }, + // DocumentNodeDefinition { + // identifier: "Load Image", + // category: "Web Request", + // node_template: NodeTemplate { + // document_node: DocumentNode { + // implementation: DocumentNodeImplementation::Network(NodeNetwork { + // exports: vec![NodeInput::node(NodeId(1), 0)], + // nodes: [ + // DocumentNode { + // inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], + // manual_composition: Some(concrete!(Context)), + // implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER), + // ..Default::default() + // }, + // DocumentNode { + // inputs: vec![NodeInput::node(NodeId(0), 0)], + // manual_composition: Some(concrete!(Context)), + // implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER), + // ..Default::default() + // }, + // ] + // .into_iter() + // .enumerate() + // .map(|(id, node)| (NodeId(id as u64), node)) + // .collect(), + // ..Default::default() + // }), + // inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::String("graphite:null".to_string()), false)], + // ..Default::default() + // }, + // persistent_node_metadata: DocumentNodePersistentMetadata { + // input_metadata: vec![("Empty", "TODO").into(), ("URL", "TODO").into()], + // output_names: vec!["Image".to_string()], + // network_metadata: Some(NodeNetworkMetadata { + // persistent_metadata: NodeNetworkPersistentMetadata { + // node_metadata: [ + // DocumentNodeMetadata { + // persistent_metadata: DocumentNodePersistentMetadata { + // display_name: "Load Resource".to_string(), + // node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), + // ..Default::default() + // }, + // ..Default::default() + // }, + // DocumentNodeMetadata { + // persistent_metadata: DocumentNodePersistentMetadata { + // display_name: "Decode Image".to_string(), + // node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), + // ..Default::default() + // }, + // ..Default::default() + // }, + // ] + // .into_iter() + // .enumerate() + // .map(|(id, node)| (NodeId(id as u64), node)) + // .collect(), + // ..Default::default() + // }, + // ..Default::default() + // }), + // ..Default::default() + // }, + // }, + // description: Cow::Borrowed("Loads an image from a given URL"), + // properties: None, + // }, #[cfg(feature = "gpu")] DocumentNodeDefinition { identifier: "Create Canvas", diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index ec6025fc8e..8bee2952b1 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -2279,18 +2279,23 @@ impl NodeGraphMessageHandler { let locked = network_interface.is_locked(&node_id, breadcrumb_network_path); let errors = None; // TODO: Recursive traversal from export over all protonodes and match metadata with error - // self - // .node_graph_errors - // .iter() - // .find(|error| error.stable_node_id == node_id_path) - // .map(|error| format!("{:?}", error.error.clone())) - // .or_else(|| { - // if self.node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) { - // Some("Node graph type error within this node".to_string()) - // } else { - // None - // } - // }); + self.node_graph_errors + .iter() + .find(|error| match &error.original_location { + graph_craft::proto::OriginalLocation::Value(_) => false, + graph_craft::proto::OriginalLocation::Node(node_ids) => node_ids == &node_id_path, + }) + .map(|error| format!("{:?}", error.error.clone())) + .or_else(|| { + if self.node_graph_errors.iter().any(|error| match &error.original_location { + graph_craft::proto::OriginalLocation::Value(_) => false, + graph_craft::proto::OriginalLocation::Node(node_ids) => node_ids.starts_with(&node_id_path), + }) { + Some("Node graph type error within this node".to_string()) + } else { + None + } + }); nodes.push(FrontendNode { id: node_id, diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index adac91970c..a215bda3d7 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -13,14 +13,6 @@ pub struct PropertiesPanelMessageHandlerData<'a> { pub document_name: &'a str, } -use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; -use graph_craft::document::NodeId; -pub struct PropertiesPanelMessageHandlerData<'a> { - pub network_interface: &'a mut NodeNetworkInterface, - pub selection_network_path: &'a [NodeId], - pub document_name: &'a str, -} - #[derive(Debug, Clone, Default, ExtractField)] pub struct PropertiesPanelMessageHandler {} diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index f8d3f22a47..71ac76c1da 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -11,11 +11,13 @@ use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode; use bezier_rs::Subpath; use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{DocumentNode, DocumentNodeImplementation, InputConnector, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork, OutputConnector}; +use graph_craft::document::{AbsoluteInputConnector, DocumentNode, DocumentNodeImplementation, InputConnector, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork, OutputConnector}; +use graph_craft::proto::OriginalLocation; use graph_craft::{Type, concrete}; +use graphene_std::NodeIOTypes; use graphene_std::math::quad::Quad; use graphene_std::transform::Footprint; -use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; +use graphene_std::uuid::{NodeId, SNI}; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::{PointId, VectorData, VectorModificationType}; use interpreted_executor::node_registry::NODE_REGISTRY; @@ -35,10 +37,11 @@ pub struct NodeNetworkInterface { /// Stores the document network's structural topology. Should automatically kept in sync by the setter methods when changes to the document network are made. #[serde(skip)] document_metadata: DocumentMetadata, - /// All input/output types based on the compiled network. + /// All input types based on the compiled network for protonodes. + /// The types for values inputs can be resolved from the tagged value /// TODO: Move to portfolio message handler #[serde(skip)] - pub resolved_types: HashMap>, + pub resolved_types: HashMap, #[serde(skip)] transaction_status: TransactionStatus, #[serde(skip)] @@ -490,8 +493,8 @@ impl NodeNetworkInterface { } /// Try and get the [`DocumentNodeDefinition`] for a node - pub fn node_definition(&self, node_id: NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeDefinition> { - let metadata = self.node_metadata(&node_id, network_path)?; + pub fn node_definition(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeDefinition> { + let metadata = self.node_metadata(node_id, network_path)?; resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) } @@ -512,63 +515,6 @@ impl NodeNetworkInterface { } } - pub fn downstream_caller_from_output(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> { - match output_connector { - OutputConnector::Node { node_id, output_index } => match self.implementation(node_id, network_path)? { - DocumentNodeImplementation::Network(_) => { - let mut nested_path = network_path.to_vec(); - nested_path.push(*node_id); - self.downstream_caller_from_input(&InputConnector::Export(*output_index), &nested_path) - } - DocumentNodeImplementation::ProtoNode(_) => self.node_metadata(&node_id, network_path)?.transient_metadata.caller.as_ref(), - DocumentNodeImplementation::Extract => todo!(), - }, - OutputConnector::Import(import_index) => { - let mut encapsulating_path = network_path.to_vec(); - let node_id = encapsulating_path.pop().expect("No imports in document network"); - self.downstream_caller_from_input(&InputConnector::node(node_id, *import_index), &encapsulating_path) - } - } - } - // Returns the path and input index to the protonode which called the input, which has to be the same every time is is called for a given input. - // This has to be done by iterating upstream, since a downstream traversal may lead to an uncompiled branch. - // This requires that value inputs store their caller. Caller input metadata from compilation has to be stored for - pub fn downstream_caller_from_input(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> { - // Cases: Node/Value input to protonode, Node/Value input to network node - let input = self.input_from_connector(input_connector, network_path)?; - let caller_input = match input { - NodeInput::Node { node_id, output_index, .. } => { - match self.implementation(node_id, network_path)? { - DocumentNodeImplementation::Network(_) => { - // Continue traversal within network - let mut nested_path = network_path.to_vec(); - nested_path.push(*node_id); - self.downstream_caller_from_input(&InputConnector::Export(*output_index), &nested_path) - } - DocumentNodeImplementation::ProtoNode(_) => self.node_metadata(node_id, network_path)?.transient_metadata.caller.as_ref(), - // If connected to a protonode, use the data in the node metadata - DocumentNodeImplementation::Extract => todo!(), - } - } - // Can either be an input to a protonode, network node, or export - NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Reflection(_) => match input_connector { - InputConnector::Node { node_id, .. } => self.transient_input_metadata(node_id, input_connector.input_index(), network_path)?.caller.as_ref(), - InputConnector::Export(export_index) => self.network_metadata(network_path)?.transient_metadata.callers.get(*export_index)?.as_ref(), - }, - NodeInput::Network { import_index, .. } => { - let mut encapsulating_path = network_path.to_vec(); - let node_id = encapsulating_path.pop().expect("No imports in document network"); - self.downstream_caller_from_input(&InputConnector::node(node_id, *import_index), &encapsulating_path) - } - NodeInput::Inline(_) => None, - }; - let Some(caller_input) = caller_input else { - log::error!("Could not get compiled caller input for input: {:?} in network: {:?}", input_connector, network_path); - return None; - }; - Some(caller_input) - } - pub fn take_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { let Some(network) = self.network_mut(network_path) else { log::error!("Could not get network in input_from_connector"); @@ -587,131 +533,186 @@ impl NodeNetworkInterface { input.map(|input| std::mem::replace(input, NodeInput::value(TaggedValue::None, true))) } - /// Guess the type from the node based on a document node default or a random protonode definition. - fn guess_type_from_node(&mut self, node_id: NodeId, input_index: usize, network_path: &[NodeId]) -> (Type, TypeSource) { - // Try and get the default value from the document node definition - if let Some(value) = self - .node_definition(node_id, network_path) - .and_then(|definition| definition.node_template.document_node.inputs.get(input_index)) - .and_then(|input| input.as_value()) - { - return (value.ty(), TypeSource::DocumentNodeDefault); - } + /// Guess the type from the node based on the tagged value, document node default, or a random protonode definition. + // fn guess_type_from_uncompiled_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { + // let Some(input) = self.input_from_connector(input_connector, network_path) else { + // return (concrete!(()), TypeSource::Error("Could not get input from connector")); + // }; - let Some(node) = self.document_node(&node_id, network_path) else { - return (concrete!(()), TypeSource::Error("node id {node_id:?} not in network {network_path:?}")); - }; + // match input { + // NodeInput::Node { node_id: upstream_node_id, output_index, .. } => { + // let input_index = input_connector.input_index(); + // // Try and get the default value from the document node definition + // if let Some(value) = self + // .node_definition(upstream_node_id, network_path) + // .and_then(|definition| definition.node_template.document_node.inputs.get(input_index)) + // .and_then(|input| input.as_value()) + // { + // return (value.ty(), TypeSource::DocumentNodeDefault); + // } - let mut node_id_path = network_path.to_vec(); - node_id_path.push(node_id); + // //Get a random protonode implementation + // let Some(node) = self.document_node(&upstream_node_id, network_path) else { + // return (concrete!(()), TypeSource::Error("node id {node_id:?} not in network {network_path:?}")); + // }; - match &node.implementation { - DocumentNodeImplementation::ProtoNode(protonode) => { - let Some(node_types) = random_protonode_implementation(protonode) else { - return (concrete!(()), TypeSource::Error("could not resolve protonode")); - }; + // let mut node_id_path = network_path.to_vec(); + // node_id_path.push(*upstream_node_id); - let Some(input_type) = node_types.inputs.get(input_index) else { - log::error!("Could not get type"); - return (concrete!(()), TypeSource::Error("could not get the protonode's input")); - }; + // match &node.implementation { + // DocumentNodeImplementation::ProtoNode(protonode) => { + // let Some(node_types) = random_protonode_implementation(protonode) else { + // return (concrete!(()), TypeSource::Error("could not resolve protonode")); + // }; - (input_type.clone(), TypeSource::RandomProtonodeImplementation) - } - DocumentNodeImplementation::Network(_network) => { - // Attempt to resolve where this import is within the nested network - let outwards_wires = self.outward_wires(&node_id_path); - let inputs_using_import = outwards_wires.and_then(|outwards_wires| outwards_wires.get(&OutputConnector::Import(input_index))); - let first_input = inputs_using_import.and_then(|input| input.first()).copied(); + // let Some(input_type) = node_types.inputs.get(input_index) else { + // log::error!("Could not get type"); + // return (concrete!(()), TypeSource::Error("could not get the protonode's input")); + // }; - if let Some(InputConnector::Node { - node_id: child_id, - input_index: child_input_index, - }) = first_input - { - let mut inner_path = network_path.to_vec(); - inner_path.push(node_id); - let result = self.guess_type_from_node(child_id, child_input_index, &inner_path); - inner_path.pop(); - return result; - } - - // Input is disconnected - (concrete!(()), TypeSource::Error("disconnected network input")) - } - _ => (concrete!(()), TypeSource::Error("implementation is not network or protonode")), - } - } + // (input_type.clone(), TypeSource::RandomProtonodeImplementation) + // } + // DocumentNodeImplementation::Network(_) => { + // // TODO: Implement type guessing when + // (concrete!(()), TypeSource::Error("disconnected network input")) + // } + // _ => (concrete!(()), TypeSource::Error("implementation is not network or protonode")), + // } + // } + // // If the current input is a tagged value, then use that + // NodeInput::Value { tagged_value, exposed } => (tagged_value.ty(), TypeSource::TaggedValue), + // NodeInput::Network { import_index, import_type } => { + // // TODO: Implement type guessing for imports + // (concrete!(()), TypeSource::Error("Cannot guess type from import")) + // } + // NodeInput::Scope(cow) => (concrete!(()), TypeSource::Scope), + // NodeInput::Reflection(document_node_metadata) => (concrete!(()), TypeSource::Reflection), + // NodeInput::Inline(inline_rust) => (inline_rust.ty.clone(), TypeSource::Inline), + // } + // } /// Get the [`Type`] for any InputConnector pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { - if let Some(NodeInput::Value { tagged_value, .. }) = self.input_from_connector(input_connector, network_path) { - return (tagged_value.ty(), TypeSource::TaggedValue); + // Try getting the compiled type + if let Some(node_io) = self.protonode_from_input(input_connector, network_path).and_then(|sni| self.resolved_types.get(&sni)) { + return (node_io.return_value.clone(), TypeSource::Compiled); } - - if let Some(compiled_type) = self - .downstream_caller_from_input(input_connector, network_path) - .and_then(|(sni, input_index)| self.resolved_types.get(sni).and_then(|protonode_input_types| protonode_input_types.get(*input_index))) - { - return (compiled_type.clone(), TypeSource::Compiled); - } - - // Resolve types from proto nodes in node_registry - let Some(node_id) = input_connector.node_id() else { - return (concrete!(()), TypeSource::Error("input connector is not a node")); - }; - - self.guess_type_from_node(node_id, input_connector.input_index(), network_path) + (concrete!(()), TypeSource::Error("Not compiled")) + // self.guess_type_from_uncompiled_input(input_connector, network_path) } pub fn output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { - if let Some(output_type) = self - .downstream_caller_from_output(output_connector, network_path) - .and_then(|(sni, input_index)| self.resolved_types.get(sni).and_then(|protonode_input_types| protonode_input_types.get(*input_index))) - { - return (output_type.clone(), TypeSource::Compiled); + // Try getting the compiled type + if let Some(node_io) = self.protonode_from_output(output_connector, network_path).and_then(|sni| self.resolved_types.get(&sni)) { + return (node_io.return_value.clone(), TypeSource::Compiled); } - (concrete!(()), TypeSource::Error("Not compiled")) + + (concrete!(()), TypeSource::DocumentNodeDefault) } - pub fn add_type(&mut self, sni: SNI, input_types: Vec) { - self.resolved_types.insert(sni, input_types); + // Iterates upstream to whatever protonode this input is connected to + pub fn protonode_from_input(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { + match self.input_from_connector(input_connector, network_path)? { + NodeInput::Node { node_id, output_index, .. } => self.protonode_from_output( + &OutputConnector::Node { + node_id: *node_id, + output_index: *output_index, + }, + network_path, + ), + NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Reflection(_) => match input_connector { + InputConnector::Node { node_id, .. } => self.transient_input_metadata(node_id, input_connector.input_index(), network_path)?.sni.clone(), + InputConnector::Export(export_index) => self.network_metadata(network_path)?.transient_metadata.export_stable_node_ids.get(*export_index)?.clone(), + }, + NodeInput::Network { import_index, .. } => { + let (encapsulating_node, encapsulating_network) = network_path.split_last().unwrap(); + self.protonode_from_input( + &InputConnector::Node { + node_id: *encapsulating_node, + input_index: *import_index, + }, + encapsulating_network, + ) + } + NodeInput::Inline(_) => None, + } + } + + pub fn protonode_from_output(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option { + match output_connector { + OutputConnector::Node { node_id, output_index } => match self.implementation(node_id, network_path)? { + DocumentNodeImplementation::Network(_) => { + let mut inner_path = network_path.to_vec(); + inner_path.push(*node_id); + self.protonode_from_input(&InputConnector::Export(*output_index), &inner_path) + } + DocumentNodeImplementation::ProtoNode(_) => self.node_metadata(node_id, network_path)?.transient_metadata.sni.clone(), + DocumentNodeImplementation::Extract => None, + }, + OutputConnector::Import(import_index) => { + let (encapsulating_node, encapsulating_network) = network_path.split_last().unwrap(); + self.protonode_from_input( + &InputConnector::Node { + node_id: *encapsulating_node, + input_index: *import_index, + }, + encapsulating_network, + ) + } + } + } + + pub fn update_sni(&mut self, original_location: OriginalLocation, sni: SNI) { + match original_location { + OriginalLocation::Value(AbsoluteInputConnector { network_path, connector }) => { + let (first, network_path) = network_path.split_first().unwrap(); + if first != &NodeId(0) { + return; + } + match connector { + InputConnector::Node { node_id, input_index } => { + let Some(metadata) = self.node_metadata_mut(&node_id, network_path) else { + log::error!("node metadata must exist when setting input caller for node {}, input index {}", node_id, input_index); + return; + }; + let Some(input_metadata) = metadata.persistent_metadata.input_metadata.get_mut(input_index) else { + log::error!("input metadata must exist when setting input caller for node {}, input index {}", node_id, input_index); + return; + }; + input_metadata.transient_metadata.sni = Some(sni); + } + InputConnector::Export(export_index) => { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { + return; + }; + network_metadata.transient_metadata.export_stable_node_ids.resize(export_index + 1, None); + network_metadata.transient_metadata.export_stable_node_ids[export_index] = Some(sni); + } + } + } + OriginalLocation::Node(network_path) => { + let (first, node_path) = network_path.split_first().unwrap(); + if first != &NodeId(0) { + return; + } + let (node_id, network_path) = node_path.split_last().unwrap(); + + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + return; + }; + metadata.transient_metadata.sni = Some(sni); + } + } + } + + pub fn add_type(&mut self, sni: SNI, compiled_type: NodeIOTypes) { + self.resolved_types.insert(sni, compiled_type); } pub fn remove_type(&mut self, sni: SNI) { self.resolved_types.remove(&sni); } - pub fn set_node_caller(&mut self, node_id: &NodeId, caller: CompiledProtonodeInput, network_path: &[NodeId]) { - let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { - return; - }; - metadata.transient_metadata.caller = Some(caller); - } - - pub fn set_input_caller(&mut self, input_connector: &InputConnector, caller: CompiledProtonodeInput, network_path: &[NodeId]) { - match input_connector { - InputConnector::Node { node_id, input_index } => { - let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { - log::error!("node metadata must exist when setting input caller for node {}, input index {}", node_id, input_index); - return; - }; - let Some(input_metadata) = metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { - log::error!("input metadata must exist when setting input caller for node {}, input index {}", node_id, input_index); - return; - }; - input_metadata.transient_metadata.caller = Some(caller); - } - InputConnector::Export(export_index) => { - let Some(network_metadata) = self.network_metadata_mut(network_path) else { - return; - }; - network_metadata.transient_metadata.callers.resize(*export_index + 1, None); - network_metadata.transient_metadata.callers[*export_index] = Some(caller); - } - } - } - pub fn valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { let InputConnector::Node { node_id, input_index } = input_connector else { // An export can have any type connected to it @@ -755,11 +756,18 @@ impl NodeNetworkInterface { implementations .iter() .filter_map(|(node_io, _)| { + // Check if the node_io is valid based on the other types let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| { - let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0; + let (input_type, type_source) = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path); + // If the other input types have been compiled, then check if the current implementation is valid + if type_source == TypeSource::Compiled { + node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type) + } else { + // If the other inputs haven't been compiled, then any implementation type is valid + true + } // Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type. // For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData - node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type) }); if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } }) @@ -1155,7 +1163,7 @@ impl NodeNetworkInterface { /// Returns the description of the node, or an empty string if it is not set. pub fn description(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { - self.node_definition(*node_id, network_path) + self.node_definition(node_id, network_path) .map(|node_definition| node_definition.description.to_string()) .filter(|description| description != "TODO") .unwrap_or_default() @@ -2761,7 +2769,7 @@ impl NodeNetworkInterface { let mut path_string = String::new(); let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0); - let input_sni = self.downstream_caller_from_input(input, network_path).map(|caller| NodeId(caller.0.0 + caller.1 as u64)); + let input_sni = self.protonode_from_input(input, network_path); Some(WirePath { path_string, data_type, @@ -6056,6 +6064,11 @@ pub enum TypeSource { TaggedValue, OuterMostExportDefault, + Scope, + Reflection, + Inline, + Extract, + Error(&'static str), } @@ -6304,7 +6317,7 @@ pub struct NodeNetworkTransientMetadata { pub rounded_network_edge_distance: TransientMetadata, // Wires from the exports pub wires: Vec>, - pub callers: Vec>, + pub export_stable_node_ids: Vec>, } #[derive(Debug, Clone)] @@ -6492,7 +6505,7 @@ impl InputPersistentMetadata { #[derive(Debug, Clone, Default)] struct InputTransientMetadata { wire: TransientMetadata, - caller: Option, + sni: Option, } // TODO: Eventually remove this migration document upgrade code @@ -6807,7 +6820,7 @@ pub struct DocumentNodeTransientMetadata { // Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer. pub node_type_metadata: NodeTypeTransientMetadata, // Stores the caller input since it will be reached through an upstream traversal, but all data is stored per input. - pub caller: Option, + pub sni: Option, } #[derive(Debug, Clone)] diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index acdfdbf8dd..087b3df85b 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -514,6 +514,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ .map(|(node_path, node)| (node_path, node.clone())) .collect::, graph_craft::document::DocumentNode)>>(); for (node_path, node) in &nodes { + let (node_id, network_path) = node_path.split_last().unwrap(); migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open); } } diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index fd9418ac96..86558d0177 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -9,7 +9,7 @@ use graphene_std::Color; use graphene_std::raster::Image; use graphene_std::renderer::RenderMetadata; use graphene_std::text::Font; -use graphene_std::uuid::CompiledProtonodeInput; +use graphene_std::uuid::{SNI}; #[impl_message(Message, Portfolio)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -31,7 +31,7 @@ pub enum PortfolioMessage { EvaluateActiveDocument, // Sends a request to introspect data in the network, and return it to the editor IntrospectActiveDocument { - inputs_to_introspect: HashSet, + nodes_to_introspect: HashSet, }, ExportActiveDocument { file_name: String, @@ -50,7 +50,7 @@ pub enum PortfolioMessage { }, ProcessIntrospectionResponse { #[serde(skip)] - introspected_inputs: IntrospectionResponse, + introspection_response: IntrospectionResponse, }, RenderThumbnails, ProcessThumbnails, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 5b0ee5334c..350e9b87b0 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -20,14 +20,13 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::node_graph_executor::{CompilationRequest, ExportConfig, NodeGraphExecutor}; use glam::{DAffine2, DVec2}; use graph_craft::document::value::EditorMetadata; -use graph_craft::document::{AbsoluteInputConnector, InputConnector, NodeInput, OutputConnector}; +use graph_craft::document::{InputConnector, NodeInput, OutputConnector}; use graphene_std::any::EditorContext; use graphene_std::application_io::TimingInformation; -use graphene_std::memo::IntrospectMode; use graphene_std::renderer::{Quad, RenderMetadata}; use graphene_std::text::Font; use graphene_std::transform::{Footprint, RenderQuality}; -use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; +use graphene_std::uuid::{NodeId, SNI}; use std::sync::Arc; #[derive(ExtractField)] @@ -57,12 +56,11 @@ pub struct PortfolioMessageHandler { pub spreadsheet: SpreadsheetMessageHandler, device_pixel_ratio: Option, pub reset_node_definitions_on_open: bool, - // Data from the node graph. Data for inputs are set to be collected on each evaluation, and added on the evaluation response - // Data from old nodes get deleted after a compilation - // Always take data after requesting it - pub introspected_data: HashMap>>, - pub introspected_call_argument: HashMap>>, - pub previous_thumbnail_data: HashMap>, + // Data from the node graph, which is populated after an introspection request. + // To access the data, schedule messages with StartIntrospectionQueue [messages] EndIntrospectionQueue + // The data is no longer accessible after EndIntrospectionQueue + pub introspected_data: HashMap>>, + pub previous_thumbnail_data: HashMap>, } #[message_handler_data] @@ -111,13 +109,18 @@ impl MessageHandler> for Portfolio self.menu_bar_message_handler.process_message(message, responses, ()); } PortfolioMessage::Spreadsheet(message) => { - self.spreadsheet.process_message( - message, - responses, - SpreadsheetMessageHandlerData { - introspected_data: &self.introspected_data, - }, - ); + if let Some(document_id) = self.active_document_id { + if let Some(document) = self.documents.get_mut(&document_id) { + self.spreadsheet.process_message( + message, + responses, + SpreadsheetMessageHandlerData { + introspected_data: &self.introspected_data, + network_interface: &document.network_interface, + }, + ); + } + } } PortfolioMessage::Document(message) => { if let Some(document_id) = self.active_document_id { @@ -445,11 +448,11 @@ impl MessageHandler> for Portfolio document_migration_upgrades(&mut document, reset_node_definitions_on_open); // Ensure each node has the metadata for its inputs - for (mut path, node) in document.network_interface.document_network().clone().recursive_nodes() { - let node_id = path.pop().unwrap(); - document.network_interface.validate_input_metadata(node_id, node, &path); - document.network_interface.validate_display_name_metadata(node_id, &path); - document.network_interface.validate_output_names(node_id, node, &path); + for (node_path, node) in document.network_interface.document_network().clone().recursive_nodes() { + let (node_id, path) = node_path.split_last().unwrap(); + document.network_interface.validate_input_metadata(&node_id, node, &path); + document.network_interface.validate_display_name_metadata(&node_id, &path); + document.network_interface.validate_output_names(&node_id, node, &path); } // Ensure layers are positioned as stacks if they are upstream siblings of another layer @@ -788,6 +791,8 @@ impl MessageHandler> for Portfolio transform_to_viewport: true, }, }); + // Also evaluate the document after compilation + responses.add_front(PortfolioMessage::EvaluateActiveDocument); } } PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => { @@ -795,43 +800,24 @@ impl MessageHandler> for Portfolio log::error!("Tried to render non-existent document: {:?}", self.active_document_id); return; }; - for (value_connectors, caller) in compilation_metadata.protonode_caller_for_values { - for AbsoluteInputConnector { network_path, connector } in value_connectors { - let (first, network_path) = network_path.split_first().unwrap(); - if first != &NodeId(0) { - continue; - } - document.network_interface.set_input_caller(&connector, caller, network_path) - } - } - for (protonode_paths, caller) in compilation_metadata.protonode_caller_for_nodes { - for protonode_path in protonode_paths { - let (first, node_path) = protonode_path.split_first().unwrap(); - if first != &NodeId(0) { - continue; - } - let (node_id, network_path) = node_path.split_last().expect("Protonode path cannot be empty"); - document.network_interface.set_node_caller(node_id, caller, &network_path) - } + for (orignal_location, sni) in compilation_metadata.original_locations { + document.network_interface.update_sni(orignal_location, sni); } for (sni, input_types) in compilation_metadata.types_to_add { document.network_interface.add_type(sni, input_types); } let mut cleared_thumbnails = Vec::new(); - for (sni, number_of_inputs) in compilation_metadata.types_to_remove { - // Removed saved type of the document node + for sni in compilation_metadata.types_to_remove { + // Removed saved type of the protonode document.network_interface.remove_type(sni); + // TODO: This does not remove thumbnails for wires to value inputs // Remove all thumbnails - for input_index in 0..number_of_inputs { - cleared_thumbnails.push(NodeId(sni.0 + input_index as u64 + 1)); - } + cleared_thumbnails.push(sni); } responses.add(FrontendMessage::UpdateThumbnails { add: Vec::new(), clear: cleared_thumbnails, }); - // Always evaluate after a recompile - responses.add(PortfolioMessage::EvaluateActiveDocument); } PortfolioMessage::EvaluateActiveDocument => { let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { @@ -911,12 +897,14 @@ impl MessageHandler> for Portfolio let RenderMetadata { upstream_footprints: footprints, local_transforms, + first_instance_source_id, click_targets, clip_targets, } = evaluation_metadata; responses.add(DocumentMessage::UpdateUpstreamTransforms { upstream_footprints: footprints, local_transforms, + first_instance_source_id, }); responses.add(DocumentMessage::UpdateClickTargets { click_targets }); responses.add(DocumentMessage::UpdateClipTargets { clip_targets }); @@ -931,41 +919,35 @@ impl MessageHandler> for Portfolio // After an evaluation, always render all thumbnails responses.add(PortfolioMessage::RenderThumbnails); } - PortfolioMessage::IntrospectActiveDocument { inputs_to_introspect } => { - self.executor.submit_node_graph_introspection(inputs_to_introspect); + PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => { + self.executor.submit_node_graph_introspection(nodes_to_introspect); } - PortfolioMessage::ProcessIntrospectionResponse { introspected_inputs } => { - for (input, mode, data) in introspected_inputs.0.into_iter() { - match mode { - IntrospectMode::Input => { - self.introspected_call_argument.insert(input, data); - } - IntrospectMode::Data => { - self.introspected_data.insert(input, data); - } - } + PortfolioMessage::ProcessIntrospectionResponse { introspection_response } => { + for (protonode, data) in introspection_response.0.into_iter() { + self.introspected_data.insert(protonode, data); } } - PortfolioMessage::ClearIntrospectedData => { - self.introspected_call_argument.clear(); - self.introspected_data.clear() - } + PortfolioMessage::ClearIntrospectedData => self.introspected_data.clear(), PortfolioMessage::RenderThumbnails => { let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { log::error!("Tried to render non-existent document: {:?}", self.active_document_id); return; }; - let mut inputs_to_render = HashSet::new(); + // All possible inputs, later check if they are connected to any nodes + let mut nodes_to_render = HashSet::new(); - // Get the protonode input for all side layer inputs connected to the export in the document network for thumbnails in the layer panel - for caller in document.network_interface.document_metadata().all_layers().filter_map(|layer| { - let input = InputConnector::Node { + // Get all inputs to render thumbnails for + // Get all protonodes for all connected side layer inputs connected to the export in the document network + for layer in document.network_interface.document_metadata().all_layers() { + let connector = InputConnector::Node { node_id: layer.to_node(), input_index: 1, }; - document.network_interface.downstream_caller_from_input(&input, &[]) - }) { - inputs_to_render.insert(*caller); + if document.network_interface.input_from_connector(&connector, &[]).is_some_and(|input| input.is_wire()) { + if let Some(compiled_input) = document.network_interface.protonode_from_input(&connector, &[]) { + nodes_to_render.insert(compiled_input); + } + } } // Save data for all inputs in the viewed node graph @@ -973,62 +955,59 @@ impl MessageHandler> for Portfolio let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else { return; }; - for (export_index, export) in viewed_network.exports.iter().enumerate() { - match document - .network_interface - .downstream_caller_from_input(&InputConnector::Export(export_index), &document.breadcrumb_network_path) - { - Some(caller) => { - // inputs_to_monitor.insert((*caller, IntrospectMode::Data)); - inputs_to_render.insert(*caller); - } - None => {} + let mut wire_stack = viewed_network + .exports + .iter() + .enumerate() + .filter_map(|(export_index, export)| export.is_wire().then_some(InputConnector::Export(export_index))) + .collect::>(); + while let Some(input_connector) = wire_stack.pop() { + let Some(input) = document.network_interface.input_from_connector(&input_connector, &document.breadcrumb_network_path) else { + log::error!("Could not get input from connector: {:?}", input_connector); + continue; }; - if let NodeInput::Node { node_id, .. } = export { - for upstream_node in document - .network_interface - .upstream_flow_back_from_nodes(vec![*node_id], &document.breadcrumb_network_path, network_interface::FlowType::UpstreamFlow) - { - let node = &viewed_network.nodes[&upstream_node]; - for (index, _) in node.inputs.iter().enumerate().filter(|(_, node_input)| node_input.is_exposed()) { - if let Some(caller) = document - .network_interface - .downstream_caller_from_input(&InputConnector::node(upstream_node, index), &document.breadcrumb_network_path) - { - // inputs_to_monitor.insert((*caller, IntrospectMode::Data)); - inputs_to_render.insert(*caller); - }; - } + if let NodeInput::Node { node_id, .. } = input { + let Some(node) = document.network_interface.document_node(node_id, &document.breadcrumb_network_path) else { + log::error!("Could not get node"); + continue; + }; + for (wire_input_index, _) in node.inputs.iter().enumerate().filter(|(_, input)| input.is_wire()) { + wire_stack.push(InputConnector::Node { + node_id: *node_id, + input_index: wire_input_index, + }) } - } + }; + let Some(protonode) = document.network_interface.protonode_from_input(&input_connector, &document.breadcrumb_network_path) else { + // The protonode has not been compiled, so it is not connected to the export + wire_stack = Vec::new(); + continue; + }; + nodes_to_render.insert(protonode); } }; - responses.add(PortfolioMessage::IntrospectActiveDocument { - inputs_to_introspect: inputs_to_render, - }); + responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect: nodes_to_render }); responses.add(Message::StartIntrospectionQueue); responses.add(PortfolioMessage::ProcessThumbnails); responses.add(Message::EndIntrospectionQueue); } PortfolioMessage::ProcessThumbnails => { let mut thumbnail_response = ThumbnailRenderResponse::default(); - for (thumbnail_input, introspected_data) in self.introspected_data.drain() { - let input_node_id = thumbnail_input.0.0 + thumbnail_input.1 as u64; - + for (thumbnail_node, introspected_data) in self.introspected_data.drain() { let Some(evaluated_data) = introspected_data else { // Input was not evaluated, do not change its thumbnail continue; }; - let previous_thumbnail_data = self.previous_thumbnail_data.get(&thumbnail_input); + let previous_thumbnail_data = self.previous_thumbnail_data.get(&thumbnail_node); match graph_craft::document::value::render_thumbnail_if_change(&evaluated_data, previous_thumbnail_data) { graph_craft::document::value::ThumbnailRenderResult::NoChange => return, - graph_craft::document::value::ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(NodeId(input_node_id)), - graph_craft::document::value::ThumbnailRenderResult::UpdateThumbnail(thumbnail) => thumbnail_response.add.push((NodeId(input_node_id), thumbnail)), + graph_craft::document::value::ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(thumbnail_node), + graph_craft::document::value::ThumbnailRenderResult::UpdateThumbnail(thumbnail) => thumbnail_response.add.push((thumbnail_node, thumbnail)), } - self.previous_thumbnail_data.insert(thumbnail_input, evaluated_data); + self.previous_thumbnail_data.insert(thumbnail_node, evaluated_data); } responses.add(FrontendMessage::UpdateThumbnails { add: thumbnail_response.add, diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs index 225520216d..96c7380199 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs @@ -1,6 +1,5 @@ use crate::messages::prelude::*; -use graph_craft::document::AbsoluteInputConnector; -use graphene_std::uuid::CompiledProtonodeInput; +use graphene_std::uuid::{NodeId, SNI}; /// The spreadsheet UI allows for instance data to be previewed. #[impl_message(Message, PortfolioMessage, Spreadsheet)] @@ -8,7 +7,8 @@ use graphene_std::uuid::CompiledProtonodeInput; pub enum SpreadsheetMessage { ToggleOpen, - UpdateLayout { inspect_input: InspectInputConnector }, + RequestUpdateLayout, + ProcessUpdateLayout { node_to_inspect: NodeId, protonode_id: SNI }, PushToInstancePath { index: usize }, TruncateInstancePath { len: usize }, @@ -23,11 +23,3 @@ pub enum VectorDataDomain { Segments, Regions, } - -/// The mapping of input where the data is extracted from to the selected input to display data for -#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)] -// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] -pub struct InspectInputConnector { - pub input_connector: AbsoluteInputConnector, - pub protonode_input: CompiledProtonodeInput, -} diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index 613c22dfd4..32f3e1e233 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -1,19 +1,23 @@ use super::VectorDataDomain; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout}; -use crate::messages::portfolio::spreadsheet::InspectInputConnector; +use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::prelude::*; use crate::messages::tool::tool_messages::tool_prelude::*; +use graph_craft::document::OutputConnector; use graphene_std::Color; use graphene_std::GraphicGroupTable; use graphene_std::instances::Instances; use graphene_std::raster::Image; -use graphene_std::uuid::CompiledProtonodeInput; +use graphene_std::uuid::{NodeId, SNI}; use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement}; use std::sync::Arc; +#[derive(ExtractField)] pub struct SpreadsheetMessageHandlerData<'a> { - pub introspected_data: &'a HashMap>>, + pub introspected_data: &'a HashMap>>, + // Network interface of the selected document + pub network_interface: &'a NodeNetworkInterface, } /// The spreadsheet UI allows for instance data to be previewed. @@ -21,50 +25,78 @@ pub struct SpreadsheetMessageHandlerData<'a> { pub struct SpreadsheetMessageHandler { /// Sets whether or not the spreadsheet is drawn. pub spreadsheet_view_open: bool, - inspect_input: Option, - // Downcasted data is not saved because the spreadsheet is simply a window into the data flowing through the input - // introspected_data: Option, + // Path to the document node that is introspected. The protonode is found by traversing from the primary output + inspection_data: Option>>, + node_to_inspect: Option, + instances_path: Vec, viewing_vector_data_domain: VectorDataDomain, } #[message_handler_data] -impl MessageHandler for SpreadsheetMessageHandler { +impl MessageHandler> for SpreadsheetMessageHandler { fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, data: SpreadsheetMessageHandlerData) { - let SpreadsheetMessageHandlerData { introspected_data } = data; + let SpreadsheetMessageHandlerData { introspected_data, network_interface } = data; match message { SpreadsheetMessage::ToggleOpen => { self.spreadsheet_view_open = !self.spreadsheet_view_open; if self.spreadsheet_view_open { - // TODO: This will not get always get data since the input could be cached, and the monitor node would not - // Be run on the evaluation. To solve this, pass in an AbsoluteNodeInput as a parameter to the compilation which tells the compiler - // to generate a random SNI in order to reset any downstream cache - // Run the graph to grab the data - responses.add(PortfolioMessage::EvaluateActiveDocument); + responses.add(SpreadsheetMessage::RequestUpdateLayout); } // Update checked UI state for open responses.add(MenuBarMessage::SendLayout); - self.update_layout(introspected_data, responses); + self.update_layout(responses); } // Queued on introspection request, runs on introspection response when the data has been sent back to the editor - SpreadsheetMessage::UpdateLayout { inspect_input } => { - self.inspect_input = Some(inspect_input); - self.update_layout(introspected_data, responses); - } + SpreadsheetMessage::RequestUpdateLayout => { + // Spreadsheet not open, no need to request + if !self.spreadsheet_view_open { + self.node_to_inspect = None; + return; + } + let selected_nodes = network_interface.selected_nodes().0; + + // Selected nodes != 1, skipping + if selected_nodes.len() != 1 { + self.node_to_inspect = None; + return; + } + + let node_to_inspect = selected_nodes[0]; + + let Some(protonode_id) = network_interface.protonode_from_output(&OutputConnector::node(node_to_inspect, 0), &[]) else { + return; + }; + + let mut nodes_to_introspect = HashSet::new(); + nodes_to_introspect.insert(protonode_id); + + responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect }); + responses.add(Message::StartIntrospectionQueue); + responses.add(SpreadsheetMessage::ProcessUpdateLayout { node_to_inspect, protonode_id }); + responses.add(Message::EndIntrospectionQueue); + + self.update_layout(responses); + } + // Runs after the introspection request has returned the Arc back to the editor + SpreadsheetMessage::ProcessUpdateLayout { node_to_inspect, protonode_id } => { + self.node_to_inspect = Some(node_to_inspect); + self.inspection_data = introspected_data.get(&protonode_id).cloned(); + } SpreadsheetMessage::PushToInstancePath { index } => { self.instances_path.push(index); - self.update_layout(introspected_data, responses); + self.update_layout(responses); } SpreadsheetMessage::TruncateInstancePath { len } => { self.instances_path.truncate(len); - self.update_layout(introspected_data, responses); + self.update_layout(responses); } SpreadsheetMessage::ViewVectorDataDomain { domain } => { self.viewing_vector_data_domain = domain; - self.update_layout(introspected_data, responses); + self.update_layout(responses); } } } @@ -75,7 +107,7 @@ impl MessageHandler for Sprea } impl SpreadsheetMessageHandler { - fn update_layout(&mut self, introspected_data: &HashMap>>, responses: &mut VecDeque) { + fn update_layout(&mut self, responses: &mut VecDeque) { responses.add(FrontendMessage::UpdateSpreadsheetState { // The node is sent when the data is available node: None, @@ -90,21 +122,21 @@ impl SpreadsheetMessageHandler { breadcrumbs: Vec::new(), vector_data_domain: self.viewing_vector_data_domain, }; - let mut layout = match &self.inspect_input { - Some(inspect_input) => { - match introspected_data.get(&inspect_input.protonode_input) { + let mut layout = match &self.node_to_inspect { + Some(_) => { + match &self.inspection_data { Some(data) => match data { - Some(instrospected_data) => match generate_layout(instrospected_data, &mut layout_data) { + Some(inspected_data) => match generate_layout(&inspected_data, &mut layout_data) { Some(layout) => layout, None => label("The introspected data is not a supported type to be displayed."), }, None => label("Introspected data is not available for this input. This input may be cached."), }, // There should always be an entry for each protonode input. If its empty then it was not requested or an error occured - None => label("Error getting introspected data"), + None => label("The output of this node could not be determined"), } } - None => label("No input selected to show data for."), + None => label("No node selected to show data for."), }; if layout_data.breadcrumbs.len() > 1 { diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index cdc8e15591..bfe23604e4 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -331,21 +331,21 @@ impl<'a> MessageHandler> for Path responses.add(ToolMessage::UpdateHints); let pivot_gizmo = self.tool_data.pivot_gizmo(); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); self.send_layout(responses, LayoutTarget::ToolOptions); } } PathOptionsUpdate::TogglePivotGizmoType(state) => { self.tool_data.pivot_gizmo.state.disabled = !state; responses.add(ToolMessage::UpdateHints); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); self.send_layout(responses, LayoutTarget::ToolOptions); } PathOptionsUpdate::TogglePivotPinned => { self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned; responses.add(ToolMessage::UpdateHints); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); self.send_layout(responses, LayoutTarget::ToolOptions); } }, @@ -2407,7 +2407,7 @@ impl Fsm for PathToolFsmState { tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap()); let pivot_gizmo = tool_data.pivot_gizmo(); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); self } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index d9d0ea5aae..ec6ddb1a18 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -289,21 +289,21 @@ impl<'a> MessageHandler> for Sele responses.add(ToolMessage::UpdateHints); let pivot_gizmo = self.tool_data.pivot_gizmo(); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); redraw_reference_pivot = true; } } SelectOptionsUpdate::TogglePivotGizmoType(state) => { self.tool_data.pivot_gizmo.state.disabled = !state; responses.add(ToolMessage::UpdateHints); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); redraw_reference_pivot = true; } SelectOptionsUpdate::TogglePivotPinned => { self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned; responses.add(ToolMessage::UpdateHints); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); redraw_reference_pivot = true; } } @@ -1255,7 +1255,7 @@ impl Fsm for SelectToolFsmState { tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); // Auto-panning let messages = [ @@ -1611,7 +1611,7 @@ impl Fsm for SelectToolFsmState { let pivot_gizmo = tool_data.pivot_gizmo(); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); self } diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 2c2e9c4294..3eaeaa1aee 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -7,10 +7,9 @@ use graph_craft::document::value::{EditorMetadata, RenderOutput, TaggedValue}; use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid}; use graph_craft::proto::GraphErrors; use graphene_std::any::EditorContext; -use graphene_std::memo::IntrospectMode; use graphene_std::renderer::format_transform_matrix; use graphene_std::text::FontCache; -use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; +use graphene_std::uuid::SNI; mod runtime_io; pub use runtime_io::NodeRuntimeIO; @@ -47,7 +46,7 @@ pub struct EvaluationResponse { } #[derive(Debug, Clone, Default)] -pub struct IntrospectionResponse(pub Vec<((NodeId, usize), IntrospectMode, Option>)>); +pub struct IntrospectionResponse(pub Vec<(SNI, Option>)>); impl PartialEq for IntrospectionResponse { fn eq(&self, _other: &Self) -> bool { @@ -132,7 +131,7 @@ impl NodeGraphExecutor { self.futures.insert(evaluation_id, evaluation_context); } - pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet) { + pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet) { if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::IntrospectionRequest(nodes_to_introspect)) { log::error!("Could not send evaluation request. {:?}", error); return; @@ -375,35 +374,3 @@ impl NodeGraphExecutor { // } // } // } - -// Passed as a scope input -#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)] -pub struct EditorMetadata { - // pub imaginate_hostname: String, - pub use_vello: bool, - pub hide_artboards: bool, - // If exporting, hide the artboard name and do not collect metadata - pub for_export: bool, - pub view_mode: graphene_core::vector::style::ViewMode, - pub transform_to_viewport: bool, -} - -unsafe impl dyn_any::StaticType for EditorMetadata { - type Static = EditorMetadata; -} - -impl Default for EditorMetadata { - fn default() -> Self { - Self { - // imaginate_hostname: "http://localhost:7860/".into(), - #[cfg(target_arch = "wasm32")] - use_vello: false, - #[cfg(not(target_arch = "wasm32"))] - use_vello: true, - hide_artboards: false, - for_export: false, - view_mode: graphene_core::vector::style::ViewMode::Normal, - transform_to_viewport: true, - } - } -} diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 29ceecc779..e44df76f21 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -1,10 +1,10 @@ use super::*; use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use glam::DVec2; +use graph_craft::ProtoNodeIdentifier; use graph_craft::document::NodeNetwork; use graph_craft::proto::GraphErrors; use graphene_std::text::FontCache; -use graphene_std::uuid::CompiledProtonodeInput; use graphene_std::wasm_application_io::WasmApplicationIo; use interpreted_executor::dynamic_executor::DynamicExecutor; use interpreted_executor::util::wrap_network_in_scope; @@ -31,9 +31,6 @@ pub struct NodeRuntime { /// Mapping of the fully-qualified node paths to their preprocessor substitutions. substitutions: HashMap, - - /// Stored in order to check for changes before sending to the frontend. - thumbnail_render_tagged_values: HashMap, } /// Messages passed from the editor thread to the node runtime thread. @@ -49,7 +46,7 @@ pub enum GraphRuntimeRequest { // ThumbnailRenderRequest(HashSet), // Request the data from a list of node inputs. For example, used by vector modify to get the data at the input of every Path node. // Can also be used by the spreadsheet/introspection system - IntrospectionRequest(HashSet), + IntrospectionRequest(HashSet), } #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -102,7 +99,7 @@ impl NodeRuntime { // self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await)); } - // TODO: This deduplication of messages will probably cause issues + // TODO: This deduplication of messages may cause issues let mut compilation = None; let mut evaluation = None; let mut introspection = None; @@ -143,20 +140,20 @@ impl NodeRuntime { self.sender.send_evaluation_response(EvaluationResponse { evaluation_id, result }); } // GraphRuntimeRequest::ThumbnailRenderRequest(_) => {} - GraphRuntimeRequest::IntrospectionRequest(inputs) => { - let mut introspected_inputs = Vec::new(); - for protonode_input in inputs { - let introspected_data = match self.executor.introspect(protonode_input, IntrospectMode::Data) { + GraphRuntimeRequest::IntrospectionRequest(nodes) => { + let mut introspected_nodes = Vec::new(); + for protonode in nodes { + let introspected_data = match self.executor.introspect(protonode, true) { Ok(introspected_data) => introspected_data, Err(e) => { - log::error!("Could not introspect input: {:?}, error: {:?}", protonode_input, e); + log::error!("Could not introspect protonode: {:?}, error: {:?}", protonode, e); continue; } }; - introspected_inputs.push((protonode_input, IntrospectMode::Data, introspected_data)); + introspected_nodes.push((protonode, introspected_data)); } - self.sender.send_introspection_response(IntrospectionResponse(introspected_inputs)); + self.sender.send_introspection_response(IntrospectionResponse(introspected_nodes)); } } } @@ -173,8 +170,8 @@ impl NodeRuntime { // Modifies the NodeNetwork so the tagged values are removed and the document nodes with protonode implementations have their protonode ids set // Needs to return a mapping of absolute input connectors to protonode callers, types for protonodes, and callers for protonodes, add/remove delta for resolved types - let (proto_network, protonode_caller_for_values, protonode_caller_for_nodes) = match scoped_network.flatten() { - Ok(network) => network, + let (proto_network, original_locations) = match scoped_network.flatten() { + Ok(result) => result, Err(e) => { log::error!("Error compiling network: {e:?}"); return Err(e); @@ -186,8 +183,7 @@ impl NodeRuntime { // Used to remove thumbnails from the mapping of SNI to rendered SVG strings on the frontend, which occurs when the SNI is removed // When native frontend rendering is possible, the strings can just be stored in the network interface for each protonode with the rest of the type metadata Ok(CompilationMetadata { - protonode_caller_for_values, - protonode_caller_for_nodes, + original_locations, types_to_add, types_to_remove, }) diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index 9b58a66be8..ff56d3d20e 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -123,8 +123,13 @@ pub fn downcast<'a, V: StaticType + 'a>(i: Box + 'a>) -> Result = Box + 'n + Send>; +#[cfg(target_arch = "wasm32")] +pub type Any<'n> = Box + 'n>; + #[cfg(feature = "alloc")] -pub fn try_downcast<'a, V: StaticType + 'a>(i: Box + 'a + Send>) -> Result, Box + 'a + Send>> { +pub fn try_downcast<'a, V: StaticType + 'a>(i: Any<'a>) -> Result, Any<'a>> { let type_id = DynAny::type_id(i.as_ref()); if type_id == core::any::TypeId::of::<::Static>() { // SAFETY: caller guarantees that T is the correct type diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 01ccd0ed13..1c0be3e561 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -51,26 +51,52 @@ pub trait ExtractAll: ExtractFootprint + ExtractDownstreamTransform + ExtractInd impl ExtractAll for T {} #[derive(Debug, Clone, PartialEq)] +#[repr(u8)] pub enum ContextDependency { - ExtractFootprint, + ExtractFootprint = 0b10000000, // Can be used by cull nodes to check if the final output would be outside the footprint viewport - ExtractDownstreamTransform, - ExtractRealTime, - ExtractAnimationTime, - ExtractIndex, - ExtractVarArgs, + ExtractDownstreamTransform = 0b01000000, + ExtractRealTime = 0b00100000, + ExtractAnimationTime = 0b00010000, + ExtractIndex = 0b00001000, + ExtractVarArgs = 0b00000100, } -pub fn all_context_dependencies() -> Vec { - vec![ - ContextDependency::ExtractFootprint, - // Can be used by cull nodes to check if the final output would be outside the footprint viewport - ContextDependency::ExtractDownstreamTransform, - ContextDependency::ExtractRealTime, - ContextDependency::ExtractAnimationTime, - ContextDependency::ExtractIndex, - ContextDependency::ExtractVarArgs, - ] +#[derive(Debug, Clone, PartialEq)] +pub struct ContextDependencies(pub u8); + +impl ContextDependencies { + pub fn all_context_dependencies() -> Self { + ContextDependencies(0b11111100) + } + + pub fn none() -> Self { + ContextDependencies(0b00000000) + } + + pub fn is_empty(&self) -> bool { + self.0 & Self::all_context_dependencies().0 == 0 + } + + pub fn from(dependencies: Vec) -> Self { + let mut new = Self::none(); + for dependency in dependencies { + new.0 |= dependency as u8 + } + new + } + + pub fn inverse(self) -> Self { + Self(!self.0) + } + + pub fn add_dependencies(&mut self, other: &Self) { + self.0 |= other.0 + } + + pub fn difference(&mut self, other: &Self) { + self.0 = (!self.0) & other.0 + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -348,19 +374,25 @@ impl OwnedContextImpl { } } - pub fn nullify(&mut self, nullify: &Vec) { - for context_dependency in nullify { - match context_dependency { - ContextDependency::ExtractFootprint => self.footprint = None, - ContextDependency::ExtractDownstreamTransform => self.downstream_transform = None, - ContextDependency::ExtractRealTime => self.real_time = None, - ContextDependency::ExtractAnimationTime => self.animation_time = None, - ContextDependency::ExtractIndex => self.index = None, - ContextDependency::ExtractVarArgs => { - self.varargs = None; - self.parent = None - } - } + pub fn nullify(&mut self, nullify: &ContextDependencies) { + if nullify.0 & (ContextDependency::ExtractFootprint as u8) != 0 { + self.footprint = None; + } + if nullify.0 & (ContextDependency::ExtractDownstreamTransform as u8) != 0 { + self.downstream_transform = None; + } + if nullify.0 & (ContextDependency::ExtractRealTime as u8) != 0 { + self.real_time = None; + } + if nullify.0 & (ContextDependency::ExtractAnimationTime as u8) != 0 { + self.animation_time = None; + } + if nullify.0 & (ContextDependency::ExtractIndex as u8) != 0 { + self.index = None; + } + if nullify.0 & (ContextDependency::ExtractVarArgs as u8) != 0 { + self.varargs = None; + self.parent = None } } } diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 80ef4af4f1..42a92059d6 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -60,17 +60,11 @@ pub trait Node<'i, Input> { std::any::type_name::() } - /// Get the call argument or output data for the monitor node on the next evaluation after set_introspect_input - /// Also returns a boolean of whether the node was evaluated - fn introspect(&self, _introspect_mode: IntrospectMode) -> Option> { + // If check if evaluated is true, then it returns None if the node has not been evaluated since the last introspection + fn introspect(&self, _check_if_evaluated: bool) -> Option> { log::warn!("Node::introspect not implemented for {}", std::any::type_name::()); None } - - // The introspect mode is set before the graph evaluation, and tells the monitor node what data to store - fn set_introspect(&self, _introspect_mode: IntrospectMode) { - log::warn!("Node::set_introspect not implemented for {}", std::any::type_name::()); - } } mod types; diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index 2f59a5e700..983482a8a0 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -12,7 +12,6 @@ use std::sync::Mutex; pub struct MonitorMemoNode { // Introspection cache, uses the hash of the nullified context with default var args // cache: Arc>>>, - // Return value cache, cache: Arc)>>>, node: CachedNode, changed_since_last_eval: Arc>, @@ -25,31 +24,7 @@ where // TODO: This should return a reference to the cached cached_value // but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty accurate xD type Output = DynFuture<'i, T>; - // fn eval(&'i self, input: I) -> Self::Output { - // let mut hasher = DefaultHasher::new(); - // input.hash(&mut hasher); - // let hash = hasher.finish(); - // if let Some(data) = self.cache.lock().unwrap().get(&hash) { - // let cloned_data = (**data).clone(); - // Box::pin(async move { cloned_data }) - // } else { - // let fut = self.node.eval(input); - // let cache = self.cache.clone(); - // Box::pin(async move { - // let value = fut.await; - // cache.lock().unwrap().insert(hash, Arc::new(value.clone())); - // value - // }) - // } - // } - - // fn introspect(&self, _introspect_mode: IntrospectMode) -> Option> { - // let mut hasher = DefaultHasher::new(); - // OwnedContextImpl::default().into_context().hash(&mut hasher); - // let hash = hasher.finish(); - // self.cache.lock().unwrap().get(&hash).map(|data| (*data).clone() as Arc) - // } fn eval(&'i self, input: I) -> Self::Output { let mut hasher = DefaultHasher::new(); input.hash(&mut hasher); @@ -69,13 +44,20 @@ where }) } } - fn introspect(&self, _introspect_mode: IntrospectMode) -> Option> { - if *self.changed_since_last_eval.lock().unwrap() { - *self.changed_since_last_eval.lock().unwrap() = false; - Some(self.cache.lock().unwrap().as_ref().expect("Cached data should always be evaluated before introspection").1.clone() as Arc) - } else { - None + + // TODO: Consider returning a reference to the entire cache so the frontend reference is automatically updated as the context changes + fn introspect(&self, check_if_evaluated: bool) -> Option> { + let mut changed = self.changed_since_last_eval.lock().unwrap(); + if check_if_evaluated { + if !*changed { + return None; + } } + *changed = false; + + let cache_guard = self.cache.lock().unwrap(); + let cached = cache_guard.as_ref().expect("Cached data should always be evaluated before introspection"); + Some(cached.1.clone() as Arc) } } @@ -230,21 +212,6 @@ where output }) } - - // After introspecting, the input/output get set to None because the Arc is moved to the editor where it can be directly accessed. - fn introspect(&self, introspect_mode: IntrospectMode) -> Option> { - match introspect_mode { - IntrospectMode::Input => self.input.lock().unwrap().take().map(|input| input as Arc), - IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Arc), - } - } - - fn set_introspect(&self, introspect_mode: IntrospectMode) { - match introspect_mode { - IntrospectMode::Input => *self.introspect_input.lock().unwrap() = true, - IntrospectMode::Data => *self.introspect_output.lock().unwrap() = true, - } - } } impl MonitorNode { diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index abf2134bdf..817f706965 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -1,4 +1,4 @@ -use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend}; +use crate::{ContextDependencies, Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend}; use dyn_any::{DynAny, StaticType}; use std::borrow::Cow; use std::collections::HashMap; @@ -109,7 +109,7 @@ pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::ne pub static NODE_METADATA: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); -pub static NODE_CONTEXT_DEPENDENCY: LazyLock>>> = LazyLock::new(|| Mutex::new(HashMap::new())); +pub static NODE_CONTEXT_DEPENDENCY: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); #[cfg(not(target_arch = "wasm32"))] pub type DynFuture<'n, T> = Pin + 'n + Send>>; @@ -290,12 +290,8 @@ where } } - fn introspect(&self, introspect_mode: crate::IntrospectMode) -> Option> { - self.node.introspect(introspect_mode) - } - - fn set_introspect(&self, introspect_mode: crate::IntrospectMode) { - self.node.set_introspect(introspect_mode); + fn introspect(&self, check_if_evaluated: bool) -> Option> { + self.node.introspect(check_if_evaluated) } fn reset(&self) { diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 5564721bfd..88431c281d 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -1,14 +1,14 @@ pub mod value; use crate::document::value::TaggedValue; -use crate::proto::{ConstructionArgs, NodeConstructionArgs, NodeValueArgs, ProtoNetwork, ProtoNode, UpstreamInputMetadata}; +use crate::proto::{ConstructionArgs, NodeConstructionArgs, OriginalLocation, ProtoNetwork, ProtoNode, UpstreamInputMetadata}; use dyn_any::DynAny; use glam::IVec2; use graphene_core::memo::MemoHashGuard; use graphene_core::registry::NODE_CONTEXT_DEPENDENCY; pub use graphene_core::uuid::generate_uuid; -use graphene_core::uuid::{CompiledProtonodeInput, NodeId, ProtonodePath, SNI}; -use graphene_core::{Context, Cow, MemoHash, ProtoNodeIdentifier, Type}; +use graphene_core::uuid::{NodeId, ProtonodePath, SNI}; +use graphene_core::{Context, ContextDependencies, Cow, MemoHash, NodeIOTypes, ProtoNodeIdentifier, Type}; use rustc_hash::FxHashMap; use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; @@ -169,6 +169,13 @@ impl NodeInput { _ => false, } } + + pub fn is_wire(&self) -> bool { + match self { + NodeInput::Node { .. } | NodeInput::Network { .. } => true, + _ => false, + } + } } #[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] @@ -510,169 +517,96 @@ impl NodeNetwork { /// Functions for compiling the network impl NodeNetwork { - // Returns a topologically sorted vec of vec of protonodes, as well as metadata extracted during compilation - // The first index represents the greatest distance to the export - // Compiles a network with one export where any scope injections are added the top level network, and the network to run is implemented as a DocumentNodeImplementation::Network - // The traversal input is the node which calls the network to be flattened. If it is None, then start from the export. - // Every value protonode stores the connector which directly called it, which is used to map the value input to the protonode caller. - // Every value input connector is mapped to its caller, and every protonode is mapped to its caller. If there are multiple, then they are compared to ensure it is the same between compilations - pub fn flatten( - &mut self, - ) -> Result< - ( - ProtoNetwork, - Vec<(Vec, CompiledProtonodeInput)>, - Vec<(Vec, CompiledProtonodeInput)>, - ), - String, - > { + // Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation + pub fn flatten(&mut self) -> Result<(ProtoNetwork, Vec<(OriginalLocation, SNI)>), String> { // These three arrays are stored in parallel let mut protonetwork = Vec::new(); // This function creates a topologically flattened network with populated original location fields but unmapped inputs - // The input to flattened protonode hashmap is used to map the inputs + // The protonode indices maps the node path to its index, used to map the caller inputs of any node to the new SNI let mut protonode_indices = HashMap::new(); self.traverse_input(&mut protonetwork, &mut HashMap::new(), &mut protonode_indices, AbsoluteInputConnector::traversal_start(), None); - // If a node with the same sni is reached, then its original location metadata must be added to the one at the higher vec index - // The index will always be a ProtonodeEntry::Protonode - let mut generated_snis_to_index = HashMap::new(); - // Generate SNI's. This gets called after all node inputs are replaced with their indices - for protonode_index in 0..protonetwork.len() { - let ProtonodeEntry::Protonode(protonode) = protonetwork.get_mut(protonode_index).unwrap() else { + // If a node with the same sni is reached, then it is deduplicated + let mut generated_snis = std::collections::HashSet::new(); + + // Editor metadata: map the original location to the stable node id for each inserted protonode + let mut original_locations = Vec::new(); + + for current_protonode_index in 0..protonetwork.len() { + let ProtonodeEntry::Protonode(protonode) = protonetwork.get_mut(current_protonode_index).unwrap() else { panic!("No protonode can be deduplicated during flattening"); }; // Generate context dependencies. If None, then it is a value node and does not require nullification - let mut protonode_context_dependencies = None; - if let ConstructionArgs::Nodes(NodeConstructionArgs { inputs, context_dependencies, .. }) = &mut protonode.construction_args { - for upstream_metadata in inputs.iter() { - let Some(upstream_metadata) = upstream_metadata else { - panic!("All inputs should be when the upstream SNI was generated"); - }; - for upstream_dependency in upstream_metadata.context_dependencies.iter().flatten() { - if !context_dependencies.contains(upstream_dependency) { - context_dependencies.push(upstream_dependency.clone()); + let (protonode_context_dependencies, upstream_is_value) = match &mut protonode.construction_args { + ConstructionArgs::Nodes(NodeConstructionArgs { inputs, context_dependencies, .. }) => { + for upstream_metadata in inputs.iter() { + let Some(upstream_metadata) = upstream_metadata else { + panic!("All inputs should be when the upstream SNI was generated"); + }; + if upstream_metadata.is_value { + context_dependencies.add_dependencies(&upstream_metadata.context_dependencies); } } - } - // The context_dependencies are now the union of all inputs and the dependencies of the protonode. Set the dependencies of each input to the difference, which represents the data to nullify - for upstream_metadata in inputs.iter_mut() { - let Some(upstream_metadata) = upstream_metadata else { - panic!("All inputs should be when the upstream SNI was generated"); - }; - match upstream_metadata.context_dependencies.as_ref() { - Some(upstream_dependencies) => { - upstream_metadata.context_dependencies = Some( - context_dependencies - .iter() - .filter(|protonode_dependency| !upstream_dependencies.contains(protonode_dependency)) - .cloned() - .collect::>(), - ) + // The context_dependencies are now the union of all inputs and the dependencies of the protonode. Set the dependencies of each input to the difference, which represents the data to nullify + for upstream_metadata in inputs.iter_mut() { + let Some(upstream_metadata) = upstream_metadata else { + panic!("All inputs should be when the upstream SNI was generated"); + }; + match upstream_metadata.is_value { + true => upstream_metadata.context_dependencies.difference(&context_dependencies), + // If the upstream node is a Value node, do not nullify the context + false => upstream_metadata.context_dependencies = ContextDependencies::none(), } - // If none then the upstream node is a Value node, so do not nullify the context - None => upstream_metadata.context_dependencies = Some(Vec::new()), } + (context_dependencies.clone(), false) } - protonode_context_dependencies = Some(context_dependencies.clone()); - } - - protonode.generate_stable_node_id(); - let current_stable_node_id = protonode.stable_node_id; - - // If the stable node id is the same as a previous node, then deduplicate - let callers = if let Some(upstream_index) = generated_snis_to_index.get(&protonode.stable_node_id) { - let ProtonodeEntry::Protonode(deduplicated_protonode) = std::mem::replace(&mut protonetwork[protonode_index], ProtonodeEntry::Deduplicated(*upstream_index)) else { - panic!("Reached protonode must not be deduplicated"); - }; - let ProtonodeEntry::Protonode(upstream_protonode) = &mut protonetwork[*upstream_index] else { - panic!("Upstream protonode must not be deduplicated"); - }; - match deduplicated_protonode.construction_args { - ConstructionArgs::Value(node_value_args) => { - let ConstructionArgs::Value(upstream_value_args) = &mut upstream_protonode.construction_args else { - panic!("Upstream protonode must match current protonode construction args"); - }; - upstream_value_args.connector_paths.extend(node_value_args.connector_paths); - } - ConstructionArgs::Nodes(node_construction_args) => { - let ConstructionArgs::Nodes(upstream_value_args) = &mut upstream_protonode.construction_args else { - panic!("Upstream protonode must match current protonode construction args"); - }; - upstream_value_args.node_paths.extend(node_construction_args.node_paths); - // The dependencies of the deduplicated node and the upstream node are the same because all inputs are the same - } - ConstructionArgs::Inline(_) => todo!(), - } - // Set the caller of the upstream node to be the minimum of all deduplicated nodes and itself - upstream_protonode.caller = deduplicated_protonode.callers.iter().chain(upstream_protonode.caller.iter()).min().cloned(); - deduplicated_protonode.callers - } else { - generated_snis_to_index.insert(protonode.stable_node_id, protonode_index); - protonode.caller = protonode.callers.iter().min().cloned(); - std::mem::take(&mut protonode.callers) + // If its a value node (or extract?) then do not nullify when calling since there is no cache node placed on the output + _ => (ContextDependencies::none(), true), }; - // This runs for all protonodes - for (caller_path, input_index) in callers { - let caller_index = protonode_indices[&caller_path]; + protonode.generate_stable_node_id(); + let stable_node_id = protonode.stable_node_id; + + // If the stable node id is the same as a previous node, then deduplicate + let (callers, original_location) = if !generated_snis.insert(stable_node_id) { + let ProtonodeEntry::Protonode(deduplicated_protonode) = std::mem::replace(&mut protonetwork[current_protonode_index], ProtonodeEntry::Deduplicated) else { + panic!("Reached protonode cannot already be deduplicated"); + }; + (deduplicated_protonode.callers, deduplicated_protonode.original_location) + } else { + ( + std::mem::take(&mut protonode.callers), + std::mem::replace(&mut protonode.original_location, OriginalLocation::Node(Vec::new())), + ) + }; + + // Map the callers inputs to the generated stable node id + for (caller, input_index) in callers { + let caller_index = protonode_indices[&caller]; let ProtonodeEntry::Protonode(caller_protonode) = &mut protonetwork[caller_index] else { panic!("Downstream caller cannot be deduplicated"); }; match &mut caller_protonode.construction_args { ConstructionArgs::Nodes(nodes) => { - assert!(caller_index > protonode_index, "Caller index must be higher than current index"); - let input_metadata: &mut Option = &mut nodes.inputs[input_index]; - if input_metadata.is_none() { - *input_metadata = Some(UpstreamInputMetadata { - input_sni: current_stable_node_id, - context_dependencies: protonode_context_dependencies.clone(), - }) - } + assert!(caller_index > current_protonode_index, "Caller index must be higher than current index"); + nodes.inputs[input_index] = Some(UpstreamInputMetadata { + input_sni: stable_node_id, + context_dependencies: protonode_context_dependencies.clone(), + is_value: upstream_is_value, + }) } // Value node cannot be a caller ConstructionArgs::Value(_) => unreachable!(), ConstructionArgs::Inline(_) => todo!(), } } + + // Map the original location to the stable node id + original_locations.push((original_location, stable_node_id)); } - // Do another traversal now that the metadata has been accumulated after deduplication - // This includes the caller of all absolute value connections which have a NodeInput::Value, as well as the caller for each protonode - let mut value_connector_callers = Vec::new(); - let mut protonode_callers = Vec::new(); - // Collect caller ids into a separate vec so that the pronetwork can be mutably iterated over to take the connector/node paths rather than cloning - let calling_protonode_ids = protonetwork - .iter() - .map(|entry| match entry { - ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id, - ProtonodeEntry::Deduplicated(upstream_protonode_index) => { - let ProtonodeEntry::Protonode(proto_node) = &protonetwork[*upstream_protonode_index] else { - panic!("Upstream protonode index must not be dedeuplicated"); - }; - proto_node.stable_node_id - } - }) - .collect::>(); - - for protonode_entry in &mut protonetwork { - if let ProtonodeEntry::Protonode(protonode) = protonode_entry { - if let Some((caller_path, caller_input_index)) = protonode.caller.as_ref() { - let caller_index = protonode_indices[caller_path]; - match &mut protonode.construction_args { - ConstructionArgs::Value(node_value_args) => { - value_connector_callers.push((std::mem::take(&mut node_value_args.connector_paths), (calling_protonode_ids[caller_index], *caller_input_index))) - } - ConstructionArgs::Nodes(node_construction_args) => { - protonode_callers.push((std::mem::take(&mut node_construction_args.node_paths), (calling_protonode_ids[caller_index], *caller_input_index))) - } - ConstructionArgs::Inline(_) => todo!(), - } - } - } - } - - Ok((ProtoNetwork::from_vec(protonetwork), value_connector_callers, protonode_callers)) + Ok((ProtoNetwork::from_vec(protonetwork), original_locations)) } fn get_input_from_absolute_connector(&mut self, traversal_input: &AbsoluteInputConnector) -> Option<&mut NodeInput> { @@ -728,7 +662,7 @@ impl NodeNetwork { protonetwork: &mut Vec, // None represents a deduplicated value node // Every time a value input is reached, it is added to a mapping so if it reached again, it can be moved to the end of the protonetwork value_protonode_indices: &mut HashMap, - // Every time a protonode is reached, is it added to a mapping so if it reached again, it can be moved to the end of the protonetwork + // Every time a protonode is reached, is it added to a mapping so if it reached again protonode_indices: &mut HashMap, // The original location of the current traversal traversal_input: AbsoluteInputConnector, @@ -827,12 +761,11 @@ impl NodeNetwork { Some((upstream_node_path.clone(), input_index)), ); } - let context_dependencies = NODE_CONTEXT_DEPENDENCY.lock().unwrap().get(identifier.name.as_ref()).cloned().unwrap_or_default(); + let context_dependencies = NODE_CONTEXT_DEPENDENCY.lock().unwrap().get(identifier.name.as_ref()).cloned().unwrap_or(ContextDependencies::none()); let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs { identifier, inputs: vec![None; number_of_inputs], context_dependencies, - node_paths: Vec::new(), }); let protonode = ProtoNode { construction_args, @@ -840,11 +773,11 @@ impl NodeNetwork { input: concrete!(Context), stable_node_id: NodeId(0), callers: Vec::new(), - caller: None, + original_location: OriginalLocation::Node(upstream_node_path.clone()), }; let new_protonode_index = protonetwork.len(); protonetwork.push(ProtonodeEntry::Protonode(protonode)); - protonode_indices.insert(upstream_node_path.clone(), new_protonode_index); + protonode_indices.insert(upstream_node_path, new_protonode_index); let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else { panic!("Inserted protonode must exist at new_protonode_index"); }; @@ -855,10 +788,6 @@ impl NodeNetwork { if let Some(traversal_start) = traversal_start { reached_protonode.callers.push(traversal_start); } - let ConstructionArgs::Nodes(args) = &mut reached_protonode.construction_args else { - panic!("Reached protonode must have Nodes construction args"); - }; - args.node_paths.push(upstream_node_path); } DocumentNodeImplementation::Extract => todo!(), } @@ -876,18 +805,15 @@ impl NodeNetwork { // Insert the protonode and traverse over inputs None => { let value_protonode = ProtoNode { - construction_args: ConstructionArgs::Value(NodeValueArgs { - value: std::mem::replace(tagged_value, TaggedValue::None.into()), - connector_paths: Vec::new(), - }), + construction_args: ConstructionArgs::Value(std::mem::replace(tagged_value, TaggedValue::None.into())), input: concrete!(Context), // Could be () stable_node_id: NodeId(0), callers: Vec::new(), - caller: None, + original_location: OriginalLocation::Value(traversal_input.clone()), }; let new_protonode_index = protonetwork.len(); protonetwork.push(ProtonodeEntry::Protonode(value_protonode)); - value_protonode_indices.insert(traversal_input.clone(), new_protonode_index); + value_protonode_indices.insert(traversal_input, new_protonode_index); let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else { panic!("Previously inserted protonode must exist at mapped protonode index"); @@ -895,15 +821,10 @@ impl NodeNetwork { protonode } }; - // Only add the traversal start if it is not the root export if let Some(traversal_start) = traversal_start { reached_protonode.callers.push(traversal_start); } - let ConstructionArgs::Value(args) = &mut reached_protonode.construction_args else { - panic!("Reached protonode must have Nodes construction args"); - }; - args.connector_paths.push(traversal_input); } // Continue traversal NodeInput::Network { import_index, .. } => { @@ -969,17 +890,15 @@ impl NodeNetwork { #[derive(Debug, Clone)] pub enum ProtonodeEntry { Protonode(ProtoNode), - // If deduplicated, then any upstream node which this node previously called needs to map to the new protonode - Deduplicated(usize), + // A node is deduplicated if it has the same stable node id, + Deduplicated, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct CompilationMetadata { // Stored for every value input in the compiled network - pub protonode_caller_for_values: Vec<(Vec, CompiledProtonodeInput)>, - // Stored for every protonode in the compiled network - pub protonode_caller_for_nodes: Vec<(Vec, CompiledProtonodeInput)>, - pub types_to_add: Vec<(SNI, Vec)>, - pub types_to_remove: Vec<(SNI, usize)>, + pub original_locations: Vec<(OriginalLocation, SNI)>, + pub types_to_add: Vec<(SNI, NodeIOTypes)>, + pub types_to_remove: Vec, } //An Input connector with a node path for unique identification @@ -1112,7 +1031,7 @@ impl<'a> Iterator for RecursiveNodeIter<'a> { } } -#[cfg(test)] +// #[cfg(test)] // mod test { // use super::*; // use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; @@ -1372,65 +1291,65 @@ impl<'a> Iterator for RecursiveNodeIter<'a> { // } // } - // fn two_node_identity() -> NodeNetwork { - // NodeNetwork { - // exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], - // nodes: [ - // ( - // NodeId(1), - // DocumentNode { - // inputs: vec![NodeInput::network(concrete!(u32), 0)], - // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), - // ..Default::default() - // }, - // ), - // ( - // NodeId(2), - // DocumentNode { - // inputs: vec![NodeInput::network(concrete!(u32), 1)], - // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), - // ..Default::default() - // }, - // ), - // ] - // .into_iter() - // .collect(), - // ..Default::default() - // } - // } +// fn two_node_identity() -> NodeNetwork { +// NodeNetwork { +// exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], +// nodes: [ +// ( +// NodeId(1), +// DocumentNode { +// inputs: vec![NodeInput::network(concrete!(u32), 0)], +// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), +// ..Default::default() +// }, +// ), +// ( +// NodeId(2), +// DocumentNode { +// inputs: vec![NodeInput::network(concrete!(u32), 1)], +// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), +// ..Default::default() +// }, +// ), +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// } +// } - // fn output_duplicate(network_outputs: Vec, result_node_input: NodeInput) -> NodeNetwork { - // let mut network = NodeNetwork { - // exports: network_outputs, - // nodes: [ - // ( - // NodeId(1), - // DocumentNode { - // inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)], - // implementation: DocumentNodeImplementation::Network(two_node_identity()), - // ..Default::default() - // }, - // ), - // ( - // NodeId(2), - // DocumentNode { - // inputs: vec![result_node_input], - // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), - // ..Default::default() - // }, - // ), - // ] - // .into_iter() - // .collect(), - // ..Default::default() - // }; - // let _new_ids = 101..; - // network.populate_dependants(); - // network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10000)); - // network.flatten_with_fns(NodeId(2), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10001)); - // network.remove_dead_nodes(0); - // network - // } +// fn output_duplicate(network_outputs: Vec, result_node_input: NodeInput) -> NodeNetwork { +// let mut network = NodeNetwork { +// exports: network_outputs, +// nodes: [ +// ( +// NodeId(1), +// DocumentNode { +// inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)], +// implementation: DocumentNodeImplementation::Network(two_node_identity()), +// ..Default::default() +// }, +// ), +// ( +// NodeId(2), +// DocumentNode { +// inputs: vec![result_node_input], +// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), +// ..Default::default() +// }, +// ), +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// }; +// let _new_ids = 101..; +// network.populate_dependants(); +// network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10000)); +// network.flatten_with_fns(NodeId(2), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10001)); +// network.remove_dead_nodes(0); +// network +// } // #[test] // fn simple_duplicate() { diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 06811c307d..ce0f513675 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -1,4 +1,5 @@ -use crate::document::{AbsoluteInputConnector, InlineRust, ProtonodeEntry, value}; +use crate::document::value::TaggedValue; +use crate::document::{AbsoluteInputConnector, InlineRust, ProtonodeEntry}; pub use graphene_core::registry::*; use graphene_core::uuid::{NodeId, ProtonodePath, SNI}; use graphene_core::*; @@ -22,11 +23,8 @@ impl ProtoNetwork { let last_entry = nodes.last().expect("Cannot compile empty protonetwork"); let output = match last_entry { ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id, - ProtonodeEntry::Deduplicated(deduplicated_index) => { - let ProtonodeEntry::Protonode(protonode) = &nodes[*deduplicated_index] else { - panic!("Deduplicated protonode must point to valid protonode"); - }; - protonode.stable_node_id + ProtonodeEntry::Deduplicated => { + panic!("Not possible for the output protonode to be deduplicated"); } }; ProtoNetwork { nodes, output } @@ -97,9 +95,10 @@ impl ProtoNetwork { #[derive(Clone, Debug)] pub struct UpstreamInputMetadata { pub input_sni: SNI, - // Context dependencies are accumulated during compilation, then replaced with whatever needs to be nullified - // If None, then the upstream node is a value node, so replace with an empty vec - pub context_dependencies: Option>, + // Context dependencies are accumulated during compilation, then replaced with the difference between the node's dependencies and the inputs dependencies + pub context_dependencies: ContextDependencies, + // If the upstream node is a value node, then do not nullify since the value nodes do not have a cache inserted after them + pub is_value: bool, } #[derive(Debug, Clone)] @@ -112,24 +111,14 @@ pub struct NodeConstructionArgs { // Starts as None, and is populated during stable node id generation pub inputs: Vec>, // The union of all input context dependencies and the nodes context dependency. Used to generate the context nullification for the editor entry point - pub context_dependencies: Vec, - // Stores the path of document nodes which correspond to it - pub node_paths: Vec, -} - -#[derive(Debug, Clone)] -pub struct NodeValueArgs { - /// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe) - /// Also stores its caller inputs, which is used to map the rendered thumbnail to the wire input - pub value: MemoHash, - // Stores all absolute input connectors which correspond to this value. - pub connector_paths: Vec, + pub context_dependencies: ContextDependencies, } #[derive(Debug, Clone)] /// Defines the arguments used to construct the boxed node struct. This is used to call the constructor function in the `node_registry.rs` file - which is hidden behind a wall of macros. pub enum ConstructionArgs { - Value(NodeValueArgs), + /// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe) + Value(MemoHash), Nodes(NodeConstructionArgs), /// Used for GPU computation to work around the limitations of rust-gpu. Inline(InlineRust), @@ -152,25 +141,28 @@ pub enum ConstructionArgs { // If the the protonode has ConstructionArgs::Value, then its identifier is not used, and is replaced with an UpcastNode with a value of the tagged value pub struct ProtoNode { pub construction_args: ConstructionArgs, + pub original_location: OriginalLocation, pub input: Type, pub stable_node_id: SNI, - // Each protonode stores the path and input index of the protonodes which called it + // Each protonode stores the input of the protonode which called it in order to map input SNI pub callers: Vec<(ProtonodePath, usize)>, - // Each protonode will finally store a single caller (the minimum of all callers), used by the editor - pub caller: Option<(ProtonodePath, usize)>, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +/// Stores the origin of the protonode in the document network``, which is either an inserted value protonode SNI for an input connector, or a protonode SNI for a protonode +pub enum OriginalLocation { + Value(AbsoluteInputConnector), + Node(ProtonodePath), } impl Default for ProtoNode { fn default() -> Self { Self { - construction_args: ConstructionArgs::Value(NodeValueArgs { - value: value::TaggedValue::U32(0).into(), - connector_paths: Vec::new(), - }), + construction_args: ConstructionArgs::Value(TaggedValue::U32(0).into()), input: concrete!(Context), stable_node_id: NodeId(0), callers: Vec::new(), - caller: None, + original_location: OriginalLocation::Node(Vec::new()), } } } @@ -183,7 +175,10 @@ impl ProtoNode { input: concrete!(Context), stable_node_id, callers: Vec::new(), - caller: None, + original_location: OriginalLocation::Value(AbsoluteInputConnector { + network_path: Vec::new(), + connector: crate::document::InputConnector::Export(0), + }), } } @@ -198,7 +193,7 @@ impl ProtoNode { } nodes.identifier.hash(&mut hasher); } - ConstructionArgs::Value(value) => value.value.hash(&mut hasher), + ConstructionArgs::Value(value) => value.hash(&mut hasher), ConstructionArgs::Inline(_) => todo!(), } @@ -214,6 +209,7 @@ pub enum GraphErrorType { NoConstructor, InvalidImplementations { inputs: String, error_inputs: Vec> }, MultipleImplementations { inputs: String, valid: Vec }, + UnresolvedType, } impl Debug for GraphErrorType { // TODO: format with the document graph context so the input index is the same as in the graph UI. @@ -257,25 +253,27 @@ impl Debug for GraphErrorType { ) } GraphErrorType::MultipleImplementations { inputs, valid } => write!(f, "Multiple implementations found ({inputs}):\n{valid:#?}"), + GraphErrorType::UnresolvedType => write!(f, "Could not determine type of node"), } } } + #[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct GraphError { - pub stable_node_id: SNI, + pub original_location: OriginalLocation, pub identifier: Cow<'static, str>, pub error: GraphErrorType, } impl GraphError { - pub fn new(node: &ProtoNode, text: impl Into) -> Self { - let identifier = match &node.construction_args { + pub fn new(construction_args: &ConstructionArgs, original_location: OriginalLocation, text: impl Into) -> Self { + let identifier = match &construction_args { ConstructionArgs::Nodes(node_construction_args) => node_construction_args.identifier.name.clone(), // Values are inserted into upcast nodes - ConstructionArgs::Value(node_value_args) => format!("{:?} Value Node", node_value_args.value.deref().ty()).into(), + ConstructionArgs::Value(value) => format!("{:?} Value Node", value.deref().ty()).into(), ConstructionArgs::Inline(_) => "Inline".into(), }; Self { - stable_node_id: node.stable_node_id, + original_location, identifier, error: text.into(), } @@ -339,10 +337,10 @@ impl TypingContext { } /// Returns the inferred types for a given node id. - pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result { + pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<(), GraphErrors> { // Return the inferred type if it is already known - if let Some(inferred) = self.inferred.get(&node_id) { - return Ok(inferred.clone()); + if self.inferred.contains_key(&node_id) { + return Ok(()); } let (inputs, id) = match node.construction_args { @@ -350,9 +348,9 @@ impl TypingContext { ConstructionArgs::Value(ref v) => { // assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context))); // TODO: This should return a reference to the value - let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.value.ty())), vec![]); - self.inferred.insert(node_id, types.clone()); - return Ok(types); + let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]); + self.inferred.insert(node_id, types); + return Ok(()); } // If the node has nodes as inputs we can infer the types from the node outputs ConstructionArgs::Nodes(ref construction_args) => { @@ -363,7 +361,7 @@ impl TypingContext { .map(|id| { self.inferred .get(&id) - .ok_or_else(|| vec![GraphError::new(node, GraphErrorType::InputNodeNotFound(id))]) + .ok_or_else(|| vec![GraphError::new(&node.construction_args, node.original_location.clone(), GraphErrorType::InputNodeNotFound(id))]) .map(|node| node.ty()) }) .collect::, GraphErrors>>()?; @@ -372,18 +370,24 @@ impl TypingContext { ConstructionArgs::Inline(ref inline) => (vec![inline.ty.clone()], &*Box::new(ProtoNodeIdentifier::new("Extract"))), }; - let impls = self.lookup.get(id).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?; + let Some(impls) = self.lookup.get(id) else { + return Err(vec![GraphError::new(&node.construction_args, node.original_location.clone(), GraphErrorType::NoImplementations)]); + }; if let Some(index) = inputs.iter().position(|p| { matches!(p, Type::Fn(_, b) if matches!(b.as_ref(), Type::Generic(_))) }) { - return Err(vec![GraphError::new(node, GraphErrorType::UnexpectedGenerics { index, inputs })]); + return Err(vec![GraphError::new( + &node.construction_args, + node.original_location.clone(), + GraphErrorType::UnexpectedGenerics { index, inputs }, + )]); } /// Checks if a proposed input to a particular (primary or secondary) input connector is valid for its type signature. /// `from` indicates the value given to a input, `to` indicates the input's allowed type as specified by its type signature. - fn valid_type(from: &Type, to: &Type) -> bool { + pub fn valid_type(from: &Type, to: &Type) -> bool { match (from, to) { // Direct comparison of two concrete types. (Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2, @@ -464,15 +468,17 @@ impl TypingContext { .map(|(i, t)| {let input_number = i + 1; format!("• Input {input_number}: {t}")}) .collect::>() .join("\n"); - Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })]) + Err(vec![GraphError::new( + &node.construction_args, + node.original_location.clone(), + GraphErrorType::InvalidImplementations { inputs, error_inputs }, + )]) } [(node_io, org_nio)] => { - let node_io = node_io.clone(); - // Save the inferred type self.inferred.insert(node_id, node_io.clone()); self.constructor.insert(node_id, impls[org_nio]); - Ok(node_io) + Ok(()) } // If two types are available and one of them accepts () an input, always choose that one [first, second] => { @@ -485,18 +491,25 @@ impl TypingContext { // Save the inferred type self.inferred.insert(node_id, node_io.clone()); self.constructor.insert(node_id, impls[orig_nio]); - return Ok(node_io.clone()); + return Ok(()); } } let inputs = [&node.input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); let valid = valid_output_types.into_iter().cloned().collect(); - Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) + Err(vec![GraphError::new( + &node.construction_args, + node.original_location.clone(), + GraphErrorType::MultipleImplementations { inputs, valid }, + )]) } - _ => { let inputs = [&node.input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); let valid = valid_output_types.into_iter().cloned().collect(); - Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) + Err(vec![GraphError::new( + &node.construction_args, + node.original_location.clone(), + GraphErrorType::MultipleImplementations { inputs, valid }, + )]) } } } diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index e572cd39ac..6a95b33560 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -3,7 +3,7 @@ use glam::DAffine2; pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode}; use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer}; use graphene_core::Context; -use graphene_core::ContextDependency; +use graphene_core::ContextDependencies; use graphene_core::NodeIO; use graphene_core::OwnedContextImpl; use graphene_core::WasmNotSend; @@ -52,25 +52,25 @@ pub fn downcast_node(n: SharedNodeContainer) -> Do DowncastBothNode::new(n) } -pub struct EditorContextToContext { - first: SharedNodeContainer, -} +// pub struct EditorContextToContext { +// first: SharedNodeContainer, +// } -impl<'i> Node<'i, Any<'i>> for EditorContextToContext { - type Output = DynFuture<'i, Any<'i>>; - fn eval(&'i self, input: Any<'i>) -> Self::Output { - Box::pin(async move { - let editor_context = dyn_any::downcast::(input).unwrap(); - self.first.eval(Box::new(editor_context.to_context())).await - }) - } -} +// impl<'i> Node<'i, Any<'i>> for EditorContextToContext { +// type Output = DynFuture<'i, Any<'i>>; +// fn eval(&'i self, input: Any<'i>) -> Self::Output { +// Box::pin(async move { +// let editor_context = dyn_any::downcast::(input).unwrap(); +// self.first.eval(Box::new(editor_context.to_context())).await +// }) +// } +// } -impl EditorContextToContext { - pub const fn new(first: SharedNodeContainer) -> Self { - EditorContextToContext { first } - } -} +// impl EditorContextToContext { +// pub const fn new(first: SharedNodeContainer) -> Self { +// EditorContextToContext { first } +// } +// } #[derive(Debug, Clone, Default)] pub struct EditorContext { @@ -101,7 +101,7 @@ unsafe impl StaticType for EditorContext { // } impl EditorContext { - pub fn to_context(&self) -> Context { + pub fn to_owned_context(&self) -> OwnedContextImpl { let mut context = OwnedContextImpl::default(); if let Some(footprint) = self.footprint { context.set_footprint(footprint); @@ -121,42 +121,44 @@ impl EditorContext { if let Some(index) = self.index { context.set_index(index); } + context // if let Some(editor_var_args) = self.editor_var_args { // let (variable_names, values) // context.set_varargs((variable_names, values)) // } - context.into_context() } } pub struct NullificationNode { first: SharedNodeContainer, - nullify: Vec, + nullify: ContextDependencies, } impl<'i> Node<'i, Any<'i>> for NullificationNode { type Output = DynFuture<'i, Any<'i>>; fn eval(&'i self, input: Any<'i>) -> Self::Output { - let new_input = match dyn_any::try_downcast::(input) { - Ok(context) => match *context { - Some(context) => { - let mut new_context = OwnedContextImpl::from(context); - new_context.nullify(&self.nullify); - Box::new(new_context.into_context()) as Any<'i> - } - None => { - let none: Context = None; - Box::new(none) as Any<'i> - } - }, - Err(other_input) => other_input, - }; - Box::pin(async move { self.first.eval(new_input).await }) + Box::pin(async move { + let new_input = match dyn_any::try_downcast::(input) { + Ok(context) => match *context { + Some(context) => { + let mut new_context: OwnedContextImpl = OwnedContextImpl::from(context); + new_context.nullify(&self.nullify); + Box::new(new_context.into_context()) as Any<'i> + } + None => { + let none: Context = None; + Box::new(none) as Any<'i> + } + }, + Err(other_input) => other_input, + }; + self.first.eval(new_input).await + }) } } impl NullificationNode { - pub fn new(first: SharedNodeContainer, nullify: Vec) -> Self { + pub fn new(first: SharedNodeContainer, nullify: ContextDependencies) -> Self { Self { first, nullify } } } diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 283b66df58..dc3b4a5413 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -1,13 +1,12 @@ use crate::node_registry::{CACHE_NODES, NODE_REGISTRY}; -use dyn_any::StaticType; +use dyn_any::{Any, StaticType}; use graph_craft::document::value::{TaggedValue, UpcastNode}; use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, UpstreamInputMetadata}; use graph_craft::proto::{GraphErrorType, GraphErrors}; use graph_craft::{Type, concrete}; -use graphene_std::Context; -use graphene_std::any::{EditorContext, EditorContextToContext, NullificationNode}; -use graphene_std::memo::IntrospectMode; -use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; +use graphene_std::any::{EditorContext, NullificationNode}; +use graphene_std::uuid::{NodeId, SNI}; +use graphene_std::{Context, ContextDependencies, NodeIOTypes}; use std::collections::{HashMap, HashSet}; use std::error::Error; use std::sync::Arc; @@ -46,18 +45,13 @@ impl DynamicExecutor { /// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible. #[cfg_attr(debug_assertions, inline(never))] - pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, Vec)>, Vec<(SNI, usize)>), GraphErrors> { + pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, NodeIOTypes)>, Vec), GraphErrors> { self.output = Some(proto_network.output); self.typing_context.update(&proto_network)?; - // A protonode id can change while having the same document path, and the path can change while having the same stable node id. - // Either way, the mapping of paths to ids and ids to paths has to be kept in sync. - // The mapping of monitor node paths has to kept in sync as well. let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?; let mut remove = Vec::new(); for sni in orphaned_proto_nodes { - if let Some(number_of_inputs) = self.tree.free_node(&sni) { - remove.push((sni, number_of_inputs)); - } + remove.push(sni); self.typing_context.remove_inference(&sni); } @@ -65,37 +59,20 @@ impl DynamicExecutor { .into_iter() .filter_map(|sni| { let Some(types) = self.typing_context.type_of(sni) else { + log::error!("Could not get type for sni: {:?}", sni); return None; }; - Some((sni, types.inputs.clone())) + Some((sni, types.clone())) }) .collect(); Ok((add_with_types, remove)) } - /// Intospect the value for that specific protonode input, returning for example the cached value for a monitor node. - pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result>, IntrospectError> { - let node = self.get_introspect_node_container(protonode_input)?; - Ok(node.introspect(introspect_mode)) - } - - pub fn set_introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) { - let Ok(node) = self.get_introspect_node_container(protonode_input) else { - log::error!("Could not get monitor node for input: {:?}", protonode_input); - return; - }; - node.set_introspect(introspect_mode); - } - - pub fn get_introspect_node_container(&self, protonode_input: CompiledProtonodeInput) -> Result { - // The SNI of the monitor nodes are the ids of the protonode + input index - let inserted_node = self.tree.nodes.get(&protonode_input.0).ok_or(IntrospectError::ProtoNodeNotFound(protonode_input))?; - let node = inserted_node - .input_introspection_entrypoints - .get(protonode_input.1) - .ok_or(IntrospectError::InputIndexOutOfBounds(protonode_input))?; - Ok(node.clone()) + // Introspect the cached output of any protonode + pub fn introspect(&self, protonode: SNI, check_if_evaluated: bool) -> Result>, IntrospectError> { + let inserted_node = self.tree.nodes.get(&protonode).ok_or(IntrospectError::ProtoNodeNotFound(protonode))?; + Ok(inserted_node.cached_protonode.introspect(check_if_evaluated)) } pub fn input_type(&self) -> Option { @@ -124,6 +101,7 @@ impl DynamicExecutor { .type_of(node_to_evaluate) .map(|node_io| node_io.call_argument.clone()) .ok_or("Could not get input type of network to execute".to_string())?; + // A node to convert the EditorContext to the Context is automatically inserted for each node at id-1 let result = match input_type { t if t == concrete!(Context) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()), @@ -162,9 +140,8 @@ impl DynamicExecutor { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum IntrospectError { PathNotFound(Vec), - ProtoNodeNotFound(CompiledProtonodeInput), - InputIndexOutOfBounds(CompiledProtonodeInput), - InvalidInputType(CompiledProtonodeInput), + ProtoNodeNotFound(SNI), + // InvalidInputType(SNI), NoData, RuntimeNotReady, IntrospectNotImplemented, @@ -174,31 +151,21 @@ impl std::fmt::Display for IntrospectError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IntrospectError::PathNotFound(path) => write!(f, "Path not found: {:?}", path), - IntrospectError::ProtoNodeNotFound(input) => write!(f, "ProtoNode not found: {:?}", input), + IntrospectError::ProtoNodeNotFound(node) => write!(f, "ProtoNode not found during: {:?}", node), IntrospectError::NoData => write!(f, "No data found for this node"), IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"), IntrospectError::IntrospectNotImplemented => write!(f, "Intospect not implemented"), - IntrospectError::InputIndexOutOfBounds(input) => write!(f, "Invalid input index: {:?}", input), - IntrospectError::InvalidInputType(input) => write!(f, "Invalid input type: {:?}", input), + // IntrospectError::InvalidInputType(input) => write!(f, "Invalid input type: {:?}", input), } } } #[derive(Clone)] struct InsertedProtonode { - // If the inserted protonode is a value node, then do not clear types when removing - is_value: bool, - // Either the value node, cache node, or protonode if output is not clone + // Either the value node, cache node if output is clone, or protonode if output is not clone cached_protonode: SharedNodeContainer, - // Value nodes are the entry points, since they can be directly evaluated - // Nodes with cloneable outputs have a cache, then editor entry point - // Nodes without cloneable outputs just have an editor entry point connected to their output - output_editor_entrypoint: SharedNodeContainer, - // Nodes with inputs store references to the entry points of the upstream node - // This is used to generate thumbnails - input_thumbnail_entrypoints: Vec, - // They also store references to the upstream cache/value node, used for introspection - input_introspection_entrypoints: Vec, + // A list of arguments in the context to nullify when executing the node + nullify_when_calling: ContextDependencies, } /// A store of dynamically typed nodes and their associated source map. @@ -243,10 +210,7 @@ impl BorrowTree { let sni = node.stable_node_id; old_nodes.remove(&sni); if !self.nodes.contains_key(&sni) { - // Do not send types for auto inserted value nodes - if matches!(node.construction_args, ConstructionArgs::Nodes(_)) { - nodes_with_new_type.push(sni) - } + nodes_with_new_type.push(sni); self.push_node(node, typing_context).await?; } } @@ -262,23 +226,35 @@ impl BorrowTree { } /// Evaluate any node in the borrow tree - pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option - where - I: StaticType + 'i + Send + Sync, - O: StaticType + 'i, - { - let node = self.nodes.get(&id)?; - let output = node.output_editor_entrypoint.eval(Box::new(input)); - dyn_any::downcast::(output.await).ok().map(|o| *o) - } + // pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option + // where + // I: StaticType + 'i + Send + Sync, + // O: StaticType + 'i, + // { + // let node = self.nodes.get(&id)?; + // let output = node.output_editor_entrypoint.eval(Box::new(input)); + // dyn_any::downcast::(output.await).ok().map(|o| *o) + // } + /// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value. /// This ensures that no borrowed data can escape the node graph. - pub async fn eval_tagged_value(&self, id: SNI, input: I) -> Result + pub async fn eval_tagged_value<'i, I>(&'i self, id: SNI, input: I) -> Result where I: StaticType + 'static + Send + Sync, { let inserted_node = self.nodes.get(&id).ok_or("Output node not found in executor")?; - let output = inserted_node.output_editor_entrypoint.eval(Box::new(input)); + + // Try convert the editor context to a nullified Context, since the Context is not StaticType + let new_input = match dyn_any::try_downcast::(Box::new(input)) { + Ok(editor_context) => { + let mut context = editor_context.to_owned_context(); + context.nullify(&inserted_node.nullify_when_calling); + Box::new(context.into_context()) as Any<'i> + } + Err(other_input) => other_input, + }; + + let output = inserted_node.cached_protonode.eval(new_input); TaggedValue::try_from_any(output.await) } @@ -337,9 +313,8 @@ impl BorrowTree { /// - Removes the node from `nodes` HashMap. /// - If the node is the primary node for its path in the `source_map`, it's also removed from there. /// - Returns `None` if the node is not found in the `nodes` HashMap. - pub fn free_node(&mut self, id: &SNI) -> Option { - let removed_node = self.nodes.remove(&id).expect(&format!("Could not remove node: {:?}", id)); - removed_node.is_value.then_some(removed_node.input_thumbnail_entrypoints.len()) + pub fn free_node(&mut self, id: &SNI) { + self.nodes.remove(&id).expect("Node could not be removed"); } /// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`. @@ -360,31 +335,37 @@ impl BorrowTree { /// Thumbnails is a mapping of the protonode input to the rendered thumbnail through the monitor cache node async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> { let sni = proto_node.stable_node_id; - // Move the value into the upcast node instead of cloning it match proto_node.construction_args { - ConstructionArgs::Value(value_args) => { - let upcasted = UpcastNode::new(value_args.value); + ConstructionArgs::Value(value) => { + let upcasted = UpcastNode::new(value); let node = Box::new(upcasted) as TypeErasedBox<'_>; - let value_node = NodeContainer::new(node); + let cached_protonode = NodeContainer::new(node); let inserted_protonode = InsertedProtonode { - is_value: true, - cached_protonode: value_node.clone(), - output_editor_entrypoint: value_node, - input_thumbnail_entrypoints: Vec::new(), - input_introspection_entrypoints: Vec::new(), + cached_protonode, + nullify_when_calling: ContextDependencies::none(), }; self.nodes.insert(sni, inserted_protonode); } ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), ConstructionArgs::Nodes(node_construction_args) => { - let construction_nodes = self.node_deps(&node_construction_args.inputs); + let Some(types) = typing_context.type_of(sni) else { + return Err(vec![GraphError::new( + &ConstructionArgs::Nodes(node_construction_args), + proto_node.original_location, + GraphErrorType::UnresolvedType, + )]); + }; - let input_thumbnail_entrypoints = construction_nodes - .iter() - .map(|inserted_protonode| inserted_protonode.output_editor_entrypoint.clone()) - .collect::>(); - let input_introspection_entrypoints = construction_nodes.iter().map(|inserted_protonode| inserted_protonode.cached_protonode.clone()).collect::>(); + let Some(constructor) = typing_context.constructor(sni) else { + return Err(vec![GraphError::new( + &ConstructionArgs::Nodes(node_construction_args), + proto_node.original_location, + GraphErrorType::NoConstructor, + )]); + }; + + let construction_nodes = self.node_deps(&node_construction_args.inputs); // Insert nullification if necessary let protonode_inputs = construction_nodes @@ -392,36 +373,20 @@ impl BorrowTree { .zip(node_construction_args.inputs.into_iter()) .map(|(inserted_protonode, input_metadata)| { let previous_input = inserted_protonode.cached_protonode.clone(); - let input_context_dependencies = input_metadata.unwrap().context_dependencies.unwrap(); - let protonode_input = if !input_context_dependencies.is_empty() { + let input_context_dependencies = input_metadata.unwrap().context_dependencies; + if !input_context_dependencies.is_empty() { let nullification_node = NullificationNode::new(previous_input, input_context_dependencies); let node = Box::new(nullification_node) as TypeErasedBox<'_>; NodeContainer::new(node) } else { previous_input - }; - protonode_input + } }) .collect::>(); - let constructor = typing_context.constructor(sni).ok_or_else(|| { - vec![GraphError { - stable_node_id: sni, - identifier: node_construction_args.identifier.name.clone(), - error: GraphErrorType::NoConstructor, - }] - })?; let node = constructor(protonode_inputs).await; let protonode = NodeContainer::new(node); - let types = typing_context.type_of(sni).ok_or_else(|| { - vec![GraphError { - stable_node_id: sni, - identifier: node_construction_args.identifier.name, - error: GraphErrorType::NoConstructor, - }] - })?; - // Insert cache nodes on the output if possible let cached_protonode = if let Some(cache_constructor) = typing_context.cache_constructor(&types.return_value.nested_type()) { let cache = cache_constructor(protonode); @@ -431,36 +396,17 @@ impl BorrowTree { protonode }; - // If the call argument is Context, insert a conversion node between EditorContext to Context so that it can be evaluated - // Also insert the nullification node to whatever the protonode is not dependent on - let mut editor_entrypoint_input = cached_protonode.clone(); - if types.call_argument == concrete!(Context) { - let nullify = graphene_std::all_context_dependencies() - .into_iter() - .filter(|dependency| !node_construction_args.context_dependencies.contains(dependency)) - .collect::>(); - if !nullify.is_empty() { - let nullification_node = NullificationNode::new(cached_protonode.clone(), nullify); - let node = Box::new(nullification_node) as TypeErasedBox<'_>; - editor_entrypoint_input = NodeContainer::new(node) - } - } - - let editor_entry_point = EditorContextToContext::new(editor_entrypoint_input); - let node = Box::new(editor_entry_point) as TypeErasedBox; - let output_editor_entrypoint = NodeContainer::new(node); + // When evaluating the node from the editor, nullify all context fields it is not dependent on + let nullify_when_calling = node_construction_args.context_dependencies.inverse(); let inserted_protonode = InsertedProtonode { - is_value: false, cached_protonode, - output_editor_entrypoint, - input_thumbnail_entrypoints, - input_introspection_entrypoints, + nullify_when_calling, }; self.nodes.insert(sni, inserted_protonode); } - }; + } Ok(()) } } @@ -476,7 +422,7 @@ mod test { let mut tree = BorrowTree::default(); let val_1_protonode = ProtoNode::value( ConstructionArgs::Value(NodeValueArgs { - value: TaggedValue::U32(2u32).into(), + value: Some(TaggedValue::U32(2u32).into()), connector_paths: Vec::new(), }), NodeId(0), @@ -485,7 +431,7 @@ mod test { let future = tree.push_node(val_1_protonode, &context); futures::executor::block_on(future).unwrap(); let _node = tree.nodes.get(&NodeId(0)).expect("Node should be added to tree"); - let result = futures::executor::block_on(tree.eval(NodeId(0), ())); - assert_eq!(result, Some(2u32)); + let result = futures::executor::block_on(tree.eval_tagged_value(NodeId(0), ())); + assert_eq!(result, Some(TaggedValue::U32(2u32).into())); } } diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index a54d001bce..12582eec86 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -375,7 +375,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result syn::Result