Simplify compilation

This commit is contained in:
Adam
2025-07-16 01:42:39 -07:00
parent f5c6b65fcc
commit 8b665d158c
24 changed files with 863 additions and 1012 deletions
+2 -2
View File
@@ -161,8 +161,8 @@ impl Dispatcher {
Message::EndIntrospectionQueue => { Message::EndIntrospectionQueue => {
self.queueing_introspection_messages = false; self.queueing_introspection_messages = false;
} }
Message::ProcessIntrospectionQueue(introspected_inputs) => { Message::ProcessIntrospectionQueue(introspection_response) => {
let update_message = PortfolioMessage::ProcessIntrospectionResponse { introspected_inputs }.into(); let update_message = PortfolioMessage::ProcessIntrospectionResponse { introspection_response }.into();
// Update the state with the render output and introspected inputs // Update the state with the render output and introspected inputs
Self::schedule_execution(&mut self.message_queues, true, [update_message]); Self::schedule_execution(&mut self.message_queues, true, [update_message]);
@@ -455,73 +455,73 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Creates a new Artboard which can be used as a working surface."), description: Cow::Borrowed("Creates a new Artboard which can be used as a working surface."),
properties: None, properties: None,
}, },
DocumentNodeDefinition { // DocumentNodeDefinition {
identifier: "Load Image", // identifier: "Load Image",
category: "Web Request", // category: "Web Request",
node_template: NodeTemplate { // node_template: NodeTemplate {
document_node: DocumentNode { // document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork { // implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)], // exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [ // nodes: [
DocumentNode { // DocumentNode {
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], // inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
manual_composition: Some(concrete!(Context)), // manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER), // implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
..Default::default() // ..Default::default()
}, // },
DocumentNode { // DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], // inputs: vec![NodeInput::node(NodeId(0), 0)],
manual_composition: Some(concrete!(Context)), // manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER), // implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
..Default::default() // ..Default::default()
}, // },
] // ]
.into_iter() // .into_iter()
.enumerate() // .enumerate()
.map(|(id, node)| (NodeId(id as u64), node)) // .map(|(id, node)| (NodeId(id as u64), node))
.collect(), // .collect(),
..Default::default() // ..Default::default()
}), // }),
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::String("graphite:null".to_string()), false)], // inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::String("graphite:null".to_string()), false)],
..Default::default() // ..Default::default()
}, // },
persistent_node_metadata: DocumentNodePersistentMetadata { // persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Empty", "TODO").into(), ("URL", "TODO").into()], // input_metadata: vec![("Empty", "TODO").into(), ("URL", "TODO").into()],
output_names: vec!["Image".to_string()], // output_names: vec!["Image".to_string()],
network_metadata: Some(NodeNetworkMetadata { // network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { // persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [ // node_metadata: [
DocumentNodeMetadata { // DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata { // persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Load Resource".to_string(), // display_name: "Load Resource".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), // node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default() // ..Default::default()
}, // },
..Default::default() // ..Default::default()
}, // },
DocumentNodeMetadata { // DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata { // persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Decode Image".to_string(), // display_name: "Decode Image".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), // node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default() // ..Default::default()
}, // },
..Default::default() // ..Default::default()
}, // },
] // ]
.into_iter() // .into_iter()
.enumerate() // .enumerate()
.map(|(id, node)| (NodeId(id as u64), node)) // .map(|(id, node)| (NodeId(id as u64), node))
.collect(), // .collect(),
..Default::default() // ..Default::default()
}, // },
..Default::default() // ..Default::default()
}), // }),
..Default::default() // ..Default::default()
}, // },
}, // },
description: Cow::Borrowed("Loads an image from a given URL"), // description: Cow::Borrowed("Loads an image from a given URL"),
properties: None, // properties: None,
}, // },
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Create Canvas", identifier: "Create Canvas",
@@ -2279,18 +2279,23 @@ impl NodeGraphMessageHandler {
let locked = network_interface.is_locked(&node_id, breadcrumb_network_path); 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 let errors = None; // TODO: Recursive traversal from export over all protonodes and match metadata with error
// self self.node_graph_errors
// .node_graph_errors .iter()
// .iter() .find(|error| match &error.original_location {
// .find(|error| error.stable_node_id == node_id_path) graph_craft::proto::OriginalLocation::Value(_) => false,
// .map(|error| format!("{:?}", error.error.clone())) graph_craft::proto::OriginalLocation::Node(node_ids) => node_ids == &node_id_path,
// .or_else(|| { })
// if self.node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) { .map(|error| format!("{:?}", error.error.clone()))
// Some("Node graph type error within this node".to_string()) .or_else(|| {
// } else { if self.node_graph_errors.iter().any(|error| match &error.original_location {
// None 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 { nodes.push(FrontendNode {
id: node_id, id: node_id,
@@ -13,14 +13,6 @@ pub struct PropertiesPanelMessageHandlerData<'a> {
pub document_name: &'a str, 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)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct PropertiesPanelMessageHandler {} pub struct PropertiesPanelMessageHandler {}
@@ -11,11 +11,13 @@ use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue; 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 graph_craft::{Type, concrete};
use graphene_std::NodeIOTypes;
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
use graphene_std::transform::Footprint; 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::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData, VectorModificationType}; use graphene_std::vector::{PointId, VectorData, VectorModificationType};
use interpreted_executor::node_registry::NODE_REGISTRY; 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. /// 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)] #[serde(skip)]
document_metadata: DocumentMetadata, 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 /// TODO: Move to portfolio message handler
#[serde(skip)] #[serde(skip)]
pub resolved_types: HashMap<SNI, Vec<Type>>, pub resolved_types: HashMap<SNI, NodeIOTypes>,
#[serde(skip)] #[serde(skip)]
transaction_status: TransactionStatus, transaction_status: TransactionStatus,
#[serde(skip)] #[serde(skip)]
@@ -490,8 +493,8 @@ impl NodeNetworkInterface {
} }
/// Try and get the [`DocumentNodeDefinition`] for a node /// Try and get the [`DocumentNodeDefinition`] for a node
pub fn node_definition(&self, node_id: NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeDefinition> { pub fn node_definition(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeDefinition> {
let metadata = self.node_metadata(&node_id, network_path)?; let metadata = self.node_metadata(node_id, network_path)?;
resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) 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<NodeInput> { pub fn take_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<NodeInput> {
let Some(network) = self.network_mut(network_path) else { let Some(network) = self.network_mut(network_path) else {
log::error!("Could not get network in input_from_connector"); 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))) 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. /// Guess the type from the node based on the tagged value, 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) { // fn guess_type_from_uncompiled_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) {
// Try and get the default value from the document node definition // let Some(input) = self.input_from_connector(input_connector, network_path) else {
if let Some(value) = self // return (concrete!(()), TypeSource::Error("Could not get input from connector"));
.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);
}
let Some(node) = self.document_node(&node_id, network_path) else { // match input {
return (concrete!(()), TypeSource::Error("node id {node_id:?} not in network {network_path:?}")); // 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(); // //Get a random protonode implementation
node_id_path.push(node_id); // 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 { // let mut node_id_path = network_path.to_vec();
DocumentNodeImplementation::ProtoNode(protonode) => { // node_id_path.push(*upstream_node_id);
let Some(node_types) = random_protonode_implementation(protonode) else {
return (concrete!(()), TypeSource::Error("could not resolve protonode"));
};
let Some(input_type) = node_types.inputs.get(input_index) else { // match &node.implementation {
log::error!("Could not get type"); // DocumentNodeImplementation::ProtoNode(protonode) => {
return (concrete!(()), TypeSource::Error("could not get the protonode's input")); // let Some(node_types) = random_protonode_implementation(protonode) else {
}; // return (concrete!(()), TypeSource::Error("could not resolve protonode"));
// };
(input_type.clone(), TypeSource::RandomProtonodeImplementation) // let Some(input_type) = node_types.inputs.get(input_index) else {
} // log::error!("Could not get type");
DocumentNodeImplementation::Network(_network) => { // return (concrete!(()), TypeSource::Error("could not get the protonode's input"));
// 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();
if let Some(InputConnector::Node { // (input_type.clone(), TypeSource::RandomProtonodeImplementation)
node_id: child_id, // }
input_index: child_input_index, // DocumentNodeImplementation::Network(_) => {
}) = first_input // // TODO: Implement type guessing when
{ // (concrete!(()), TypeSource::Error("disconnected network input"))
let mut inner_path = network_path.to_vec(); // }
inner_path.push(node_id); // _ => (concrete!(()), TypeSource::Error("implementation is not network or protonode")),
let result = self.guess_type_from_node(child_id, child_input_index, &inner_path); // }
inner_path.pop(); // }
return result; // // 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 } => {
// Input is disconnected // // TODO: Implement type guessing for imports
(concrete!(()), TypeSource::Error("disconnected network input")) // (concrete!(()), TypeSource::Error("Cannot guess type from import"))
} // }
_ => (concrete!(()), TypeSource::Error("implementation is not network or protonode")), // 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 /// Get the [`Type`] for any InputConnector
pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { 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) { // Try getting the compiled type
return (tagged_value.ty(), TypeSource::TaggedValue); 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);
} }
(concrete!(()), TypeSource::Error("Not compiled"))
if let Some(compiled_type) = self // self.guess_type_from_uncompiled_input(input_connector, network_path)
.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)
} }
pub fn output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { pub fn output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) {
if let Some(output_type) = self // Try getting the compiled type
.downstream_caller_from_output(output_connector, network_path) if let Some(node_io) = self.protonode_from_output(output_connector, network_path).and_then(|sni| self.resolved_types.get(&sni)) {
.and_then(|(sni, input_index)| self.resolved_types.get(sni).and_then(|protonode_input_types| protonode_input_types.get(*input_index))) return (node_io.return_value.clone(), TypeSource::Compiled);
{
return (output_type.clone(), TypeSource::Compiled);
} }
(concrete!(()), TypeSource::Error("Not compiled"))
(concrete!(()), TypeSource::DocumentNodeDefault)
} }
pub fn add_type(&mut self, sni: SNI, input_types: Vec<Type>) { // Iterates upstream to whatever protonode this input is connected to
self.resolved_types.insert(sni, input_types); pub fn protonode_from_input(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<SNI> {
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<SNI> {
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) { pub fn remove_type(&mut self, sni: SNI) {
self.resolved_types.remove(&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<Type> { pub fn valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<Type> {
let InputConnector::Node { node_id, input_index } = input_connector else { let InputConnector::Node { node_id, input_index } = input_connector else {
// An export can have any type connected to it // An export can have any type connected to it
@@ -755,11 +756,18 @@ impl NodeNetworkInterface {
implementations implementations
.iter() .iter()
.filter_map(|(node_io, _)| { .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 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. // 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 // 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 } 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. /// 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 { 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()) .map(|node_definition| node_definition.description.to_string())
.filter(|description| description != "TODO") .filter(|description| description != "TODO")
.unwrap_or_default() .unwrap_or_default()
@@ -2761,7 +2769,7 @@ impl NodeNetworkInterface {
let mut path_string = String::new(); let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); 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 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 { Some(WirePath {
path_string, path_string,
data_type, data_type,
@@ -6056,6 +6064,11 @@ pub enum TypeSource {
TaggedValue, TaggedValue,
OuterMostExportDefault, OuterMostExportDefault,
Scope,
Reflection,
Inline,
Extract,
Error(&'static str), Error(&'static str),
} }
@@ -6304,7 +6317,7 @@ pub struct NodeNetworkTransientMetadata {
pub rounded_network_edge_distance: TransientMetadata<NetworkEdgeDistance>, pub rounded_network_edge_distance: TransientMetadata<NetworkEdgeDistance>,
// Wires from the exports // Wires from the exports
pub wires: Vec<TransientMetadata<WirePathUpdate>>, pub wires: Vec<TransientMetadata<WirePathUpdate>>,
pub callers: Vec<Option<CompiledProtonodeInput>>, pub export_stable_node_ids: Vec<Option<SNI>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -6492,7 +6505,7 @@ impl InputPersistentMetadata {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct InputTransientMetadata { struct InputTransientMetadata {
wire: TransientMetadata<WirePathUpdate>, wire: TransientMetadata<WirePathUpdate>,
caller: Option<CompiledProtonodeInput>, sni: Option<SNI>,
} }
// TODO: Eventually remove this migration document upgrade code // 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. // 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, 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. // Stores the caller input since it will be reached through an upstream traversal, but all data is stored per input.
pub caller: Option<CompiledProtonodeInput>, pub sni: Option<SNI>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -514,6 +514,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
.map(|(node_path, node)| (node_path, node.clone())) .map(|(node_path, node)| (node_path, node.clone()))
.collect::<Vec<(Vec<NodeId>, graph_craft::document::DocumentNode)>>(); .collect::<Vec<(Vec<NodeId>, graph_craft::document::DocumentNode)>>();
for (node_path, node) in &nodes { 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); migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open);
} }
} }
@@ -9,7 +9,7 @@ use graphene_std::Color;
use graphene_std::raster::Image; use graphene_std::raster::Image;
use graphene_std::renderer::RenderMetadata; use graphene_std::renderer::RenderMetadata;
use graphene_std::text::Font; use graphene_std::text::Font;
use graphene_std::uuid::CompiledProtonodeInput; use graphene_std::uuid::{SNI};
#[impl_message(Message, Portfolio)] #[impl_message(Message, Portfolio)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -31,7 +31,7 @@ pub enum PortfolioMessage {
EvaluateActiveDocument, EvaluateActiveDocument,
// Sends a request to introspect data in the network, and return it to the editor // Sends a request to introspect data in the network, and return it to the editor
IntrospectActiveDocument { IntrospectActiveDocument {
inputs_to_introspect: HashSet<CompiledProtonodeInput>, nodes_to_introspect: HashSet<SNI>,
}, },
ExportActiveDocument { ExportActiveDocument {
file_name: String, file_name: String,
@@ -50,7 +50,7 @@ pub enum PortfolioMessage {
}, },
ProcessIntrospectionResponse { ProcessIntrospectionResponse {
#[serde(skip)] #[serde(skip)]
introspected_inputs: IntrospectionResponse, introspection_response: IntrospectionResponse,
}, },
RenderThumbnails, RenderThumbnails,
ProcessThumbnails, ProcessThumbnails,
@@ -20,14 +20,13 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
use crate::node_graph_executor::{CompilationRequest, ExportConfig, NodeGraphExecutor}; use crate::node_graph_executor::{CompilationRequest, ExportConfig, NodeGraphExecutor};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::document::value::EditorMetadata; 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::any::EditorContext;
use graphene_std::application_io::TimingInformation; use graphene_std::application_io::TimingInformation;
use graphene_std::memo::IntrospectMode;
use graphene_std::renderer::{Quad, RenderMetadata}; use graphene_std::renderer::{Quad, RenderMetadata};
use graphene_std::text::Font; use graphene_std::text::Font;
use graphene_std::transform::{Footprint, RenderQuality}; use graphene_std::transform::{Footprint, RenderQuality};
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; use graphene_std::uuid::{NodeId, SNI};
use std::sync::Arc; use std::sync::Arc;
#[derive(ExtractField)] #[derive(ExtractField)]
@@ -57,12 +56,11 @@ pub struct PortfolioMessageHandler {
pub spreadsheet: SpreadsheetMessageHandler, pub spreadsheet: SpreadsheetMessageHandler,
device_pixel_ratio: Option<f64>, device_pixel_ratio: Option<f64>,
pub reset_node_definitions_on_open: bool, 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 the node graph, which is populated after an introspection request.
// Data from old nodes get deleted after a compilation // To access the data, schedule messages with StartIntrospectionQueue [messages] EndIntrospectionQueue
// Always take data after requesting it // The data is no longer accessible after EndIntrospectionQueue
pub introspected_data: HashMap<CompiledProtonodeInput, Option<Arc<dyn std::any::Any + Send + Sync>>>, pub introspected_data: HashMap<SNI, Option<Arc<dyn std::any::Any + Send + Sync>>>,
pub introspected_call_argument: HashMap<CompiledProtonodeInput, Option<Arc<dyn std::any::Any + Send + Sync>>>, pub previous_thumbnail_data: HashMap<SNI, Arc<dyn std::any::Any + Send + Sync>>,
pub previous_thumbnail_data: HashMap<CompiledProtonodeInput, Arc<dyn std::any::Any + Send + Sync>>,
} }
#[message_handler_data] #[message_handler_data]
@@ -111,13 +109,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
self.menu_bar_message_handler.process_message(message, responses, ()); self.menu_bar_message_handler.process_message(message, responses, ());
} }
PortfolioMessage::Spreadsheet(message) => { PortfolioMessage::Spreadsheet(message) => {
self.spreadsheet.process_message( if let Some(document_id) = self.active_document_id {
message, if let Some(document) = self.documents.get_mut(&document_id) {
responses, self.spreadsheet.process_message(
SpreadsheetMessageHandlerData { message,
introspected_data: &self.introspected_data, responses,
}, SpreadsheetMessageHandlerData {
); introspected_data: &self.introspected_data,
network_interface: &document.network_interface,
},
);
}
}
} }
PortfolioMessage::Document(message) => { PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id { if let Some(document_id) = self.active_document_id {
@@ -445,11 +448,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_migration_upgrades(&mut document, reset_node_definitions_on_open); document_migration_upgrades(&mut document, reset_node_definitions_on_open);
// Ensure each node has the metadata for its inputs // Ensure each node has the metadata for its inputs
for (mut path, node) in document.network_interface.document_network().clone().recursive_nodes() { for (node_path, node) in document.network_interface.document_network().clone().recursive_nodes() {
let node_id = path.pop().unwrap(); let (node_id, path) = node_path.split_last().unwrap();
document.network_interface.validate_input_metadata(node_id, node, &path); document.network_interface.validate_input_metadata(&node_id, node, &path);
document.network_interface.validate_display_name_metadata(node_id, &path); document.network_interface.validate_display_name_metadata(&node_id, &path);
document.network_interface.validate_output_names(node_id, node, &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 // Ensure layers are positioned as stacks if they are upstream siblings of another layer
@@ -788,6 +791,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
transform_to_viewport: true, transform_to_viewport: true,
}, },
}); });
// Also evaluate the document after compilation
responses.add_front(PortfolioMessage::EvaluateActiveDocument);
} }
} }
PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => { PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => {
@@ -795,43 +800,24 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
log::error!("Tried to render non-existent document: {:?}", self.active_document_id); log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return; return;
}; };
for (value_connectors, caller) in compilation_metadata.protonode_caller_for_values { for (orignal_location, sni) in compilation_metadata.original_locations {
for AbsoluteInputConnector { network_path, connector } in value_connectors { document.network_interface.update_sni(orignal_location, sni);
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 (sni, input_types) in compilation_metadata.types_to_add { for (sni, input_types) in compilation_metadata.types_to_add {
document.network_interface.add_type(sni, input_types); document.network_interface.add_type(sni, input_types);
} }
let mut cleared_thumbnails = Vec::new(); let mut cleared_thumbnails = Vec::new();
for (sni, number_of_inputs) in compilation_metadata.types_to_remove { for sni in compilation_metadata.types_to_remove {
// Removed saved type of the document node // Removed saved type of the protonode
document.network_interface.remove_type(sni); document.network_interface.remove_type(sni);
// TODO: This does not remove thumbnails for wires to value inputs
// Remove all thumbnails // Remove all thumbnails
for input_index in 0..number_of_inputs { cleared_thumbnails.push(sni);
cleared_thumbnails.push(NodeId(sni.0 + input_index as u64 + 1));
}
} }
responses.add(FrontendMessage::UpdateThumbnails { responses.add(FrontendMessage::UpdateThumbnails {
add: Vec::new(), add: Vec::new(),
clear: cleared_thumbnails, clear: cleared_thumbnails,
}); });
// Always evaluate after a recompile
responses.add(PortfolioMessage::EvaluateActiveDocument);
} }
PortfolioMessage::EvaluateActiveDocument => { PortfolioMessage::EvaluateActiveDocument => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
@@ -911,12 +897,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let RenderMetadata { let RenderMetadata {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
first_instance_source_id,
click_targets, click_targets,
clip_targets, clip_targets,
} = evaluation_metadata; } = evaluation_metadata;
responses.add(DocumentMessage::UpdateUpstreamTransforms { responses.add(DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
first_instance_source_id,
}); });
responses.add(DocumentMessage::UpdateClickTargets { click_targets }); responses.add(DocumentMessage::UpdateClickTargets { click_targets });
responses.add(DocumentMessage::UpdateClipTargets { clip_targets }); responses.add(DocumentMessage::UpdateClipTargets { clip_targets });
@@ -931,41 +919,35 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// After an evaluation, always render all thumbnails // After an evaluation, always render all thumbnails
responses.add(PortfolioMessage::RenderThumbnails); responses.add(PortfolioMessage::RenderThumbnails);
} }
PortfolioMessage::IntrospectActiveDocument { inputs_to_introspect } => { PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => {
self.executor.submit_node_graph_introspection(inputs_to_introspect); self.executor.submit_node_graph_introspection(nodes_to_introspect);
} }
PortfolioMessage::ProcessIntrospectionResponse { introspected_inputs } => { PortfolioMessage::ProcessIntrospectionResponse { introspection_response } => {
for (input, mode, data) in introspected_inputs.0.into_iter() { for (protonode, data) in introspection_response.0.into_iter() {
match mode { self.introspected_data.insert(protonode, data);
IntrospectMode::Input => {
self.introspected_call_argument.insert(input, data);
}
IntrospectMode::Data => {
self.introspected_data.insert(input, data);
}
}
} }
} }
PortfolioMessage::ClearIntrospectedData => { PortfolioMessage::ClearIntrospectedData => self.introspected_data.clear(),
self.introspected_call_argument.clear();
self.introspected_data.clear()
}
PortfolioMessage::RenderThumbnails => { PortfolioMessage::RenderThumbnails => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { 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); log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return; 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 // Get all inputs to render thumbnails for
for caller in document.network_interface.document_metadata().all_layers().filter_map(|layer| { // Get all protonodes for all connected side layer inputs connected to the export in the document network
let input = InputConnector::Node { for layer in document.network_interface.document_metadata().all_layers() {
let connector = InputConnector::Node {
node_id: layer.to_node(), node_id: layer.to_node(),
input_index: 1, input_index: 1,
}; };
document.network_interface.downstream_caller_from_input(&input, &[]) 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, &[]) {
inputs_to_render.insert(*caller); nodes_to_render.insert(compiled_input);
}
}
} }
// Save data for all inputs in the viewed node graph // Save data for all inputs in the viewed node graph
@@ -973,62 +955,59 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else { let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else {
return; return;
}; };
for (export_index, export) in viewed_network.exports.iter().enumerate() { let mut wire_stack = viewed_network
match document .exports
.network_interface .iter()
.downstream_caller_from_input(&InputConnector::Export(export_index), &document.breadcrumb_network_path) .enumerate()
{ .filter_map(|(export_index, export)| export.is_wire().then_some(InputConnector::Export(export_index)))
Some(caller) => { .collect::<Vec<_>>();
// inputs_to_monitor.insert((*caller, IntrospectMode::Data)); while let Some(input_connector) = wire_stack.pop() {
inputs_to_render.insert(*caller); 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);
None => {} continue;
}; };
if let NodeInput::Node { node_id, .. } = export { if let NodeInput::Node { node_id, .. } = input {
for upstream_node in document let Some(node) = document.network_interface.document_node(node_id, &document.breadcrumb_network_path) else {
.network_interface log::error!("Could not get node");
.upstream_flow_back_from_nodes(vec![*node_id], &document.breadcrumb_network_path, network_interface::FlowType::UpstreamFlow) continue;
{ };
let node = &viewed_network.nodes[&upstream_node]; for (wire_input_index, _) in node.inputs.iter().enumerate().filter(|(_, input)| input.is_wire()) {
for (index, _) in node.inputs.iter().enumerate().filter(|(_, node_input)| node_input.is_exposed()) { wire_stack.push(InputConnector::Node {
if let Some(caller) = document node_id: *node_id,
.network_interface input_index: wire_input_index,
.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);
};
}
} }
} };
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 { responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect: nodes_to_render });
inputs_to_introspect: inputs_to_render,
});
responses.add(Message::StartIntrospectionQueue); responses.add(Message::StartIntrospectionQueue);
responses.add(PortfolioMessage::ProcessThumbnails); responses.add(PortfolioMessage::ProcessThumbnails);
responses.add(Message::EndIntrospectionQueue); responses.add(Message::EndIntrospectionQueue);
} }
PortfolioMessage::ProcessThumbnails => { PortfolioMessage::ProcessThumbnails => {
let mut thumbnail_response = ThumbnailRenderResponse::default(); let mut thumbnail_response = ThumbnailRenderResponse::default();
for (thumbnail_input, introspected_data) in self.introspected_data.drain() { for (thumbnail_node, introspected_data) in self.introspected_data.drain() {
let input_node_id = thumbnail_input.0.0 + thumbnail_input.1 as u64;
let Some(evaluated_data) = introspected_data else { let Some(evaluated_data) = introspected_data else {
// Input was not evaluated, do not change its thumbnail // Input was not evaluated, do not change its thumbnail
continue; 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) { 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::NoChange => return,
graph_craft::document::value::ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(NodeId(input_node_id)), graph_craft::document::value::ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(thumbnail_node),
graph_craft::document::value::ThumbnailRenderResult::UpdateThumbnail(thumbnail) => thumbnail_response.add.push((NodeId(input_node_id), thumbnail)), 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 { responses.add(FrontendMessage::UpdateThumbnails {
add: thumbnail_response.add, add: thumbnail_response.add,
@@ -1,6 +1,5 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graph_craft::document::AbsoluteInputConnector; use graphene_std::uuid::{NodeId, SNI};
use graphene_std::uuid::CompiledProtonodeInput;
/// The spreadsheet UI allows for instance data to be previewed. /// The spreadsheet UI allows for instance data to be previewed.
#[impl_message(Message, PortfolioMessage, Spreadsheet)] #[impl_message(Message, PortfolioMessage, Spreadsheet)]
@@ -8,7 +7,8 @@ use graphene_std::uuid::CompiledProtonodeInput;
pub enum SpreadsheetMessage { pub enum SpreadsheetMessage {
ToggleOpen, ToggleOpen,
UpdateLayout { inspect_input: InspectInputConnector }, RequestUpdateLayout,
ProcessUpdateLayout { node_to_inspect: NodeId, protonode_id: SNI },
PushToInstancePath { index: usize }, PushToInstancePath { index: usize },
TruncateInstancePath { len: usize }, TruncateInstancePath { len: usize },
@@ -23,11 +23,3 @@ pub enum VectorDataDomain {
Segments, Segments,
Regions, 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,
}
@@ -1,19 +1,23 @@
use super::VectorDataDomain; use super::VectorDataDomain;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout}; 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::prelude::*;
use crate::messages::tool::tool_messages::tool_prelude::*; use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::OutputConnector;
use graphene_std::Color; use graphene_std::Color;
use graphene_std::GraphicGroupTable; use graphene_std::GraphicGroupTable;
use graphene_std::instances::Instances; use graphene_std::instances::Instances;
use graphene_std::raster::Image; 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::vector::{VectorData, VectorDataTable};
use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement}; use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement};
use std::sync::Arc; use std::sync::Arc;
#[derive(ExtractField)]
pub struct SpreadsheetMessageHandlerData<'a> { pub struct SpreadsheetMessageHandlerData<'a> {
pub introspected_data: &'a HashMap<CompiledProtonodeInput, Option<Arc<dyn std::any::Any + Send + Sync>>>, pub introspected_data: &'a HashMap<SNI, Option<Arc<dyn std::any::Any + Send + Sync>>>,
// Network interface of the selected document
pub network_interface: &'a NodeNetworkInterface,
} }
/// The spreadsheet UI allows for instance data to be previewed. /// The spreadsheet UI allows for instance data to be previewed.
@@ -21,50 +25,78 @@ pub struct SpreadsheetMessageHandlerData<'a> {
pub struct SpreadsheetMessageHandler { pub struct SpreadsheetMessageHandler {
/// Sets whether or not the spreadsheet is drawn. /// Sets whether or not the spreadsheet is drawn.
pub spreadsheet_view_open: bool, pub spreadsheet_view_open: bool,
inspect_input: Option<InspectInputConnector>, // Path to the document node that is introspected. The protonode is found by traversing from the primary output
// Downcasted data is not saved because the spreadsheet is simply a window into the data flowing through the input inspection_data: Option<Option<Arc<dyn std::any::Any + Send + Sync>>>,
// introspected_data: Option<TaggedValue>, node_to_inspect: Option<NodeId>,
instances_path: Vec<usize>, instances_path: Vec<usize>,
viewing_vector_data_domain: VectorDataDomain, viewing_vector_data_domain: VectorDataDomain,
} }
#[message_handler_data] #[message_handler_data]
impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData> for SpreadsheetMessageHandler { impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData<'_>> for SpreadsheetMessageHandler {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, data: SpreadsheetMessageHandlerData) { fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, data: SpreadsheetMessageHandlerData) {
let SpreadsheetMessageHandlerData { introspected_data } = data; let SpreadsheetMessageHandlerData { introspected_data, network_interface } = data;
match message { match message {
SpreadsheetMessage::ToggleOpen => { SpreadsheetMessage::ToggleOpen => {
self.spreadsheet_view_open = !self.spreadsheet_view_open; self.spreadsheet_view_open = !self.spreadsheet_view_open;
if 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 responses.add(SpreadsheetMessage::RequestUpdateLayout);
// 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);
} }
// Update checked UI state for open // Update checked UI state for open
responses.add(MenuBarMessage::SendLayout); 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 // Queued on introspection request, runs on introspection response when the data has been sent back to the editor
SpreadsheetMessage::UpdateLayout { inspect_input } => { SpreadsheetMessage::RequestUpdateLayout => {
self.inspect_input = Some(inspect_input); // Spreadsheet not open, no need to request
self.update_layout(introspected_data, responses); 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 } => { SpreadsheetMessage::PushToInstancePath { index } => {
self.instances_path.push(index); self.instances_path.push(index);
self.update_layout(introspected_data, responses); self.update_layout(responses);
} }
SpreadsheetMessage::TruncateInstancePath { len } => { SpreadsheetMessage::TruncateInstancePath { len } => {
self.instances_path.truncate(len); self.instances_path.truncate(len);
self.update_layout(introspected_data, responses); self.update_layout(responses);
} }
SpreadsheetMessage::ViewVectorDataDomain { domain } => { SpreadsheetMessage::ViewVectorDataDomain { domain } => {
self.viewing_vector_data_domain = domain; self.viewing_vector_data_domain = domain;
self.update_layout(introspected_data, responses); self.update_layout(responses);
} }
} }
} }
@@ -75,7 +107,7 @@ impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData> for Sprea
} }
impl SpreadsheetMessageHandler { impl SpreadsheetMessageHandler {
fn update_layout(&mut self, introspected_data: &HashMap<CompiledProtonodeInput, Option<Arc<dyn std::any::Any + Send + Sync>>>, responses: &mut VecDeque<Message>) { fn update_layout(&mut self, responses: &mut VecDeque<Message>) {
responses.add(FrontendMessage::UpdateSpreadsheetState { responses.add(FrontendMessage::UpdateSpreadsheetState {
// The node is sent when the data is available // The node is sent when the data is available
node: None, node: None,
@@ -90,21 +122,21 @@ impl SpreadsheetMessageHandler {
breadcrumbs: Vec::new(), breadcrumbs: Vec::new(),
vector_data_domain: self.viewing_vector_data_domain, vector_data_domain: self.viewing_vector_data_domain,
}; };
let mut layout = match &self.inspect_input { let mut layout = match &self.node_to_inspect {
Some(inspect_input) => { Some(_) => {
match introspected_data.get(&inspect_input.protonode_input) { match &self.inspection_data {
Some(data) => match 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, Some(layout) => layout,
None => label("The introspected data is not a supported type to be displayed."), 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."), 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 // 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 { if layout_data.breadcrumbs.len() > 1 {
@@ -331,21 +331,21 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
let pivot_gizmo = self.tool_data.pivot_gizmo(); let pivot_gizmo = self.tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
self.send_layout(responses, LayoutTarget::ToolOptions); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
} }
PathOptionsUpdate::TogglePivotGizmoType(state) => { PathOptionsUpdate::TogglePivotGizmoType(state) => {
self.tool_data.pivot_gizmo.state.disabled = !state; self.tool_data.pivot_gizmo.state.disabled = !state;
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
self.send_layout(responses, LayoutTarget::ToolOptions); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
PathOptionsUpdate::TogglePivotPinned => { PathOptionsUpdate::TogglePivotPinned => {
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned; self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
self.send_layout(responses, LayoutTarget::ToolOptions); self.send_layout(responses, LayoutTarget::ToolOptions);
} }
}, },
@@ -2407,7 +2407,7 @@ impl Fsm for PathToolFsmState {
tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap()); tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap());
let pivot_gizmo = tool_data.pivot_gizmo(); let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
self self
} }
@@ -289,21 +289,21 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Sele
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
let pivot_gizmo = self.tool_data.pivot_gizmo(); let pivot_gizmo = self.tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
redraw_reference_pivot = true; redraw_reference_pivot = true;
} }
} }
SelectOptionsUpdate::TogglePivotGizmoType(state) => { SelectOptionsUpdate::TogglePivotGizmoType(state) => {
self.tool_data.pivot_gizmo.state.disabled = !state; self.tool_data.pivot_gizmo.state.disabled = !state;
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
redraw_reference_pivot = true; redraw_reference_pivot = true;
} }
SelectOptionsUpdate::TogglePivotPinned => { SelectOptionsUpdate::TogglePivotPinned => {
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned; self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
redraw_reference_pivot = true; redraw_reference_pivot = true;
} }
} }
@@ -1255,7 +1255,7 @@ impl Fsm for SelectToolFsmState {
tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position); tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
// Auto-panning // Auto-panning
let messages = [ let messages = [
@@ -1611,7 +1611,7 @@ impl Fsm for SelectToolFsmState {
let pivot_gizmo = tool_data.pivot_gizmo(); let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
self self
} }
+3 -36
View File
@@ -7,10 +7,9 @@ use graph_craft::document::value::{EditorMetadata, RenderOutput, TaggedValue};
use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid}; use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graphene_std::any::EditorContext; use graphene_std::any::EditorContext;
use graphene_std::memo::IntrospectMode;
use graphene_std::renderer::format_transform_matrix; use graphene_std::renderer::format_transform_matrix;
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; use graphene_std::uuid::SNI;
mod runtime_io; mod runtime_io;
pub use runtime_io::NodeRuntimeIO; pub use runtime_io::NodeRuntimeIO;
@@ -47,7 +46,7 @@ pub struct EvaluationResponse {
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct IntrospectionResponse(pub Vec<((NodeId, usize), IntrospectMode, Option<Arc<dyn std::any::Any + Send + Sync>>)>); pub struct IntrospectionResponse(pub Vec<(SNI, Option<Arc<dyn std::any::Any + Send + Sync>>)>);
impl PartialEq for IntrospectionResponse { impl PartialEq for IntrospectionResponse {
fn eq(&self, _other: &Self) -> bool { fn eq(&self, _other: &Self) -> bool {
@@ -132,7 +131,7 @@ impl NodeGraphExecutor {
self.futures.insert(evaluation_id, evaluation_context); self.futures.insert(evaluation_id, evaluation_context);
} }
pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet<CompiledProtonodeInput>) { pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet<SNI>) {
if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::IntrospectionRequest(nodes_to_introspect)) { if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::IntrospectionRequest(nodes_to_introspect)) {
log::error!("Could not send evaluation request. {:?}", error); log::error!("Could not send evaluation request. {:?}", error);
return; 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,
}
}
}
+13 -17
View File
@@ -1,10 +1,10 @@
use super::*; use super::*;
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use glam::DVec2; use glam::DVec2;
use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::NodeNetwork; use graph_craft::document::NodeNetwork;
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::wasm_application_io::WasmApplicationIo; use graphene_std::wasm_application_io::WasmApplicationIo;
use interpreted_executor::dynamic_executor::DynamicExecutor; use interpreted_executor::dynamic_executor::DynamicExecutor;
use interpreted_executor::util::wrap_network_in_scope; 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. /// Mapping of the fully-qualified node paths to their preprocessor substitutions.
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>, substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
/// Stored in order to check for changes before sending to the frontend.
thumbnail_render_tagged_values: HashMap<CompiledProtonodeInput, TaggedValue>,
} }
/// Messages passed from the editor thread to the node runtime thread. /// Messages passed from the editor thread to the node runtime thread.
@@ -49,7 +46,7 @@ pub enum GraphRuntimeRequest {
// ThumbnailRenderRequest(HashSet<CompiledProtonodeInput>), // ThumbnailRenderRequest(HashSet<CompiledProtonodeInput>),
// 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. // 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 // Can also be used by the spreadsheet/introspection system
IntrospectionRequest(HashSet<CompiledProtonodeInput>), IntrospectionRequest(HashSet<SNI>),
} }
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -102,7 +99,7 @@ impl NodeRuntime {
// self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await)); // 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 compilation = None;
let mut evaluation = None; let mut evaluation = None;
let mut introspection = None; let mut introspection = None;
@@ -143,20 +140,20 @@ impl NodeRuntime {
self.sender.send_evaluation_response(EvaluationResponse { evaluation_id, result }); self.sender.send_evaluation_response(EvaluationResponse { evaluation_id, result });
} }
// GraphRuntimeRequest::ThumbnailRenderRequest(_) => {} // GraphRuntimeRequest::ThumbnailRenderRequest(_) => {}
GraphRuntimeRequest::IntrospectionRequest(inputs) => { GraphRuntimeRequest::IntrospectionRequest(nodes) => {
let mut introspected_inputs = Vec::new(); let mut introspected_nodes = Vec::new();
for protonode_input in inputs { for protonode in nodes {
let introspected_data = match self.executor.introspect(protonode_input, IntrospectMode::Data) { let introspected_data = match self.executor.introspect(protonode, true) {
Ok(introspected_data) => introspected_data, Ok(introspected_data) => introspected_data,
Err(e) => { Err(e) => {
log::error!("Could not introspect input: {:?}, error: {:?}", protonode_input, e); log::error!("Could not introspect protonode: {:?}, error: {:?}", protonode, e);
continue; 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 // 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 // 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() { let (proto_network, original_locations) = match scoped_network.flatten() {
Ok(network) => network, Ok(result) => result,
Err(e) => { Err(e) => {
log::error!("Error compiling network: {e:?}"); log::error!("Error compiling network: {e:?}");
return Err(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 // 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 // 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 { Ok(CompilationMetadata {
protonode_caller_for_values, original_locations,
protonode_caller_for_nodes,
types_to_add, types_to_add,
types_to_remove, types_to_remove,
}) })
+6 -1
View File
@@ -123,8 +123,13 @@ pub fn downcast<'a, V: StaticType + 'a>(i: Box<dyn DynAny<'a> + 'a>) -> Result<B
} }
} }
#[cfg(not(target_arch = "wasm32"))]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;
#[cfg(target_arch = "wasm32")]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn try_downcast<'a, V: StaticType + 'a>(i: Box<dyn DynAny<'a> + 'a + Send>) -> Result<Box<V>, Box<dyn DynAny<'a> + 'a + Send>> { pub fn try_downcast<'a, V: StaticType + 'a>(i: Any<'a>) -> Result<Box<V>, Any<'a>> {
let type_id = DynAny::type_id(i.as_ref()); let type_id = DynAny::type_id(i.as_ref());
if type_id == core::any::TypeId::of::<<V as StaticType>::Static>() { if type_id == core::any::TypeId::of::<<V as StaticType>::Static>() {
// SAFETY: caller guarantees that T is the correct type // SAFETY: caller guarantees that T is the correct type
+61 -29
View File
@@ -51,26 +51,52 @@ pub trait ExtractAll: ExtractFootprint + ExtractDownstreamTransform + ExtractInd
impl<T: ?Sized + ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {} impl<T: ?Sized + ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[repr(u8)]
pub enum ContextDependency { pub enum ContextDependency {
ExtractFootprint, ExtractFootprint = 0b10000000,
// Can be used by cull nodes to check if the final output would be outside the footprint viewport // Can be used by cull nodes to check if the final output would be outside the footprint viewport
ExtractDownstreamTransform, ExtractDownstreamTransform = 0b01000000,
ExtractRealTime, ExtractRealTime = 0b00100000,
ExtractAnimationTime, ExtractAnimationTime = 0b00010000,
ExtractIndex, ExtractIndex = 0b00001000,
ExtractVarArgs, ExtractVarArgs = 0b00000100,
} }
pub fn all_context_dependencies() -> Vec<ContextDependency> { #[derive(Debug, Clone, PartialEq)]
vec![ pub struct ContextDependencies(pub u8);
ContextDependency::ExtractFootprint,
// Can be used by cull nodes to check if the final output would be outside the footprint viewport impl ContextDependencies {
ContextDependency::ExtractDownstreamTransform, pub fn all_context_dependencies() -> Self {
ContextDependency::ExtractRealTime, ContextDependencies(0b11111100)
ContextDependency::ExtractAnimationTime, }
ContextDependency::ExtractIndex,
ContextDependency::ExtractVarArgs, 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<ContextDependency>) -> 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)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -348,19 +374,25 @@ impl OwnedContextImpl {
} }
} }
pub fn nullify(&mut self, nullify: &Vec<ContextDependency>) { pub fn nullify(&mut self, nullify: &ContextDependencies) {
for context_dependency in nullify { if nullify.0 & (ContextDependency::ExtractFootprint as u8) != 0 {
match context_dependency { self.footprint = None;
ContextDependency::ExtractFootprint => self.footprint = None, }
ContextDependency::ExtractDownstreamTransform => self.downstream_transform = None, if nullify.0 & (ContextDependency::ExtractDownstreamTransform as u8) != 0 {
ContextDependency::ExtractRealTime => self.real_time = None, self.downstream_transform = None;
ContextDependency::ExtractAnimationTime => self.animation_time = None, }
ContextDependency::ExtractIndex => self.index = None, if nullify.0 & (ContextDependency::ExtractRealTime as u8) != 0 {
ContextDependency::ExtractVarArgs => { self.real_time = None;
self.varargs = None; }
self.parent = 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
} }
} }
} }
+2 -8
View File
@@ -60,17 +60,11 @@ pub trait Node<'i, Input> {
std::any::type_name::<Self>() std::any::type_name::<Self>()
} }
/// Get the call argument or output data for the monitor node on the next evaluation after set_introspect_input // If check if evaluated is true, then it returns None if the node has not been evaluated since the last introspection
/// Also returns a boolean of whether the node was evaluated fn introspect(&self, _check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
log::warn!("Node::introspect not implemented for {}", std::any::type_name::<Self>()); log::warn!("Node::introspect not implemented for {}", std::any::type_name::<Self>());
None 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::<Self>());
}
} }
mod types; mod types;
+13 -46
View File
@@ -12,7 +12,6 @@ use std::sync::Mutex;
pub struct MonitorMemoNode<T, CachedNode> { pub struct MonitorMemoNode<T, CachedNode> {
// Introspection cache, uses the hash of the nullified context with default var args // Introspection cache, uses the hash of the nullified context with default var args
// cache: Arc<Mutex<std::collections::HashMap<u64, Arc<T>>>>, // cache: Arc<Mutex<std::collections::HashMap<u64, Arc<T>>>>,
// Return value cache,
cache: Arc<Mutex<Option<(u64, Arc<T>)>>>, cache: Arc<Mutex<Option<(u64, Arc<T>)>>>,
node: CachedNode, node: CachedNode,
changed_since_last_eval: Arc<Mutex<bool>>, changed_since_last_eval: Arc<Mutex<bool>>,
@@ -25,31 +24,7 @@ where
// TODO: This should return a reference to the cached cached_value // 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 // but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty accurate xD
type Output = DynFuture<'i, T>; 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<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
// 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<dyn std::any::Any + Send + Sync>)
// }
fn eval(&'i self, input: I) -> Self::Output { fn eval(&'i self, input: I) -> Self::Output {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
input.hash(&mut hasher); input.hash(&mut hasher);
@@ -69,13 +44,20 @@ where
}) })
} }
} }
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
if *self.changed_since_last_eval.lock().unwrap() { // TODO: Consider returning a reference to the entire cache so the frontend reference is automatically updated as the context changes
*self.changed_since_last_eval.lock().unwrap() = false; fn introspect(&self, check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
Some(self.cache.lock().unwrap().as_ref().expect("Cached data should always be evaluated before introspection").1.clone() as Arc<dyn std::any::Any + Send + Sync>) let mut changed = self.changed_since_last_eval.lock().unwrap();
} else { if check_if_evaluated {
None 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<dyn std::any::Any + Send + Sync>)
} }
} }
@@ -230,21 +212,6 @@ where
output 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<Arc<dyn std::any::Any + Send + Sync>> {
match introspect_mode {
IntrospectMode::Input => self.input.lock().unwrap().take().map(|input| input as Arc<dyn std::any::Any + Send + Sync>),
IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Arc<dyn std::any::Any + Send + Sync>),
}
}
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<I, O, N> MonitorNode<I, O, N> { impl<I, O, N> MonitorNode<I, O, N> {
+4 -8
View File
@@ -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 dyn_any::{DynAny, StaticType};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
@@ -109,7 +109,7 @@ pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::ne
pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new())); pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
pub static NODE_CONTEXT_DEPENDENCY: LazyLock<Mutex<HashMap<String, Vec<crate::ContextDependency>>>> = LazyLock::new(|| Mutex::new(HashMap::new())); pub static NODE_CONTEXT_DEPENDENCY: LazyLock<Mutex<HashMap<String, ContextDependencies>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>; pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
@@ -290,12 +290,8 @@ where
} }
} }
fn introspect(&self, introspect_mode: crate::IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> { fn introspect(&self, check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.node.introspect(introspect_mode) self.node.introspect(check_if_evaluated)
}
fn set_introspect(&self, introspect_mode: crate::IntrospectMode) {
self.node.set_introspect(introspect_mode);
} }
fn reset(&self) { fn reset(&self) {
+141 -222
View File
@@ -1,14 +1,14 @@
pub mod value; pub mod value;
use crate::document::value::TaggedValue; 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 dyn_any::DynAny;
use glam::IVec2; use glam::IVec2;
use graphene_core::memo::MemoHashGuard; use graphene_core::memo::MemoHashGuard;
use graphene_core::registry::NODE_CONTEXT_DEPENDENCY; use graphene_core::registry::NODE_CONTEXT_DEPENDENCY;
pub use graphene_core::uuid::generate_uuid; pub use graphene_core::uuid::generate_uuid;
use graphene_core::uuid::{CompiledProtonodeInput, NodeId, ProtonodePath, SNI}; use graphene_core::uuid::{NodeId, ProtonodePath, SNI};
use graphene_core::{Context, Cow, MemoHash, ProtoNodeIdentifier, Type}; use graphene_core::{Context, ContextDependencies, Cow, MemoHash, NodeIOTypes, ProtoNodeIdentifier, Type};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
@@ -169,6 +169,13 @@ impl NodeInput {
_ => false, _ => false,
} }
} }
pub fn is_wire(&self) -> bool {
match self {
NodeInput::Node { .. } | NodeInput::Network { .. } => true,
_ => false,
}
}
} }
#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]
@@ -510,169 +517,96 @@ impl NodeNetwork {
/// Functions for compiling the network /// Functions for compiling the network
impl NodeNetwork { impl NodeNetwork {
// Returns a topologically sorted vec of vec of protonodes, as well as metadata extracted during compilation // Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation
// The first index represents the greatest distance to the export pub fn flatten(&mut self) -> Result<(ProtoNetwork, Vec<(OriginalLocation, SNI)>), String> {
// 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<AbsoluteInputConnector>, CompiledProtonodeInput)>,
Vec<(Vec<ProtonodePath>, CompiledProtonodeInput)>,
),
String,
> {
// These three arrays are stored in parallel // These three arrays are stored in parallel
let mut protonetwork = Vec::new(); let mut protonetwork = Vec::new();
// This function creates a topologically flattened network with populated original location fields but unmapped inputs // 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(); let mut protonode_indices = HashMap::new();
self.traverse_input(&mut protonetwork, &mut HashMap::new(), &mut protonode_indices, AbsoluteInputConnector::traversal_start(), None); 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 // If a node with the same sni is reached, then it is deduplicated
// The index will always be a ProtonodeEntry::Protonode let mut generated_snis = std::collections::HashSet::new();
let mut generated_snis_to_index = HashMap::new();
// Generate SNI's. This gets called after all node inputs are replaced with their indices // Editor metadata: map the original location to the stable node id for each inserted protonode
for protonode_index in 0..protonetwork.len() { let mut original_locations = Vec::new();
let ProtonodeEntry::Protonode(protonode) = protonetwork.get_mut(protonode_index).unwrap() else {
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"); panic!("No protonode can be deduplicated during flattening");
}; };
// Generate context dependencies. If None, then it is a value node and does not require nullification // Generate context dependencies. If None, then it is a value node and does not require nullification
let mut protonode_context_dependencies = None; let (protonode_context_dependencies, upstream_is_value) = match &mut protonode.construction_args {
if let ConstructionArgs::Nodes(NodeConstructionArgs { inputs, context_dependencies, .. }) = &mut protonode.construction_args { ConstructionArgs::Nodes(NodeConstructionArgs { inputs, context_dependencies, .. }) => {
for upstream_metadata in inputs.iter() { for upstream_metadata in inputs.iter() {
let Some(upstream_metadata) = upstream_metadata else { let Some(upstream_metadata) = upstream_metadata else {
panic!("All inputs should be when the upstream SNI was generated"); panic!("All inputs should be when the upstream SNI was generated");
}; };
for upstream_dependency in upstream_metadata.context_dependencies.iter().flatten() { if upstream_metadata.is_value {
if !context_dependencies.contains(upstream_dependency) { context_dependencies.add_dependencies(&upstream_metadata.context_dependencies);
context_dependencies.push(upstream_dependency.clone());
} }
} }
} // 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
// 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() {
for upstream_metadata in inputs.iter_mut() { let Some(upstream_metadata) = upstream_metadata else {
let Some(upstream_metadata) = upstream_metadata else { panic!("All inputs should be when the upstream SNI was generated");
panic!("All inputs should be when the upstream SNI was generated"); };
}; match upstream_metadata.is_value {
match upstream_metadata.context_dependencies.as_ref() { true => upstream_metadata.context_dependencies.difference(&context_dependencies),
Some(upstream_dependencies) => { // If the upstream node is a Value node, do not nullify the context
upstream_metadata.context_dependencies = Some( false => upstream_metadata.context_dependencies = ContextDependencies::none(),
context_dependencies
.iter()
.filter(|protonode_dependency| !upstream_dependencies.contains(protonode_dependency))
.cloned()
.collect::<Vec<_>>(),
)
} }
// 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()); // 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),
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)
}; };
// This runs for all protonodes protonode.generate_stable_node_id();
for (caller_path, input_index) in callers { let stable_node_id = protonode.stable_node_id;
let caller_index = protonode_indices[&caller_path];
// 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 { let ProtonodeEntry::Protonode(caller_protonode) = &mut protonetwork[caller_index] else {
panic!("Downstream caller cannot be deduplicated"); panic!("Downstream caller cannot be deduplicated");
}; };
match &mut caller_protonode.construction_args { match &mut caller_protonode.construction_args {
ConstructionArgs::Nodes(nodes) => { ConstructionArgs::Nodes(nodes) => {
assert!(caller_index > protonode_index, "Caller index must be higher than current index"); assert!(caller_index > current_protonode_index, "Caller index must be higher than current index");
let input_metadata: &mut Option<UpstreamInputMetadata> = &mut nodes.inputs[input_index]; nodes.inputs[input_index] = Some(UpstreamInputMetadata {
if input_metadata.is_none() { input_sni: stable_node_id,
*input_metadata = Some(UpstreamInputMetadata { context_dependencies: protonode_context_dependencies.clone(),
input_sni: current_stable_node_id, is_value: upstream_is_value,
context_dependencies: protonode_context_dependencies.clone(), })
})
}
} }
// Value node cannot be a caller // Value node cannot be a caller
ConstructionArgs::Value(_) => unreachable!(), ConstructionArgs::Value(_) => unreachable!(),
ConstructionArgs::Inline(_) => todo!(), 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 Ok((ProtoNetwork::from_vec(protonetwork), original_locations))
// 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::<Vec<_>>();
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))
} }
fn get_input_from_absolute_connector(&mut self, traversal_input: &AbsoluteInputConnector) -> Option<&mut NodeInput> { fn get_input_from_absolute_connector(&mut self, traversal_input: &AbsoluteInputConnector) -> Option<&mut NodeInput> {
@@ -728,7 +662,7 @@ impl NodeNetwork {
protonetwork: &mut Vec<ProtonodeEntry>, // None represents a deduplicated value node protonetwork: &mut Vec<ProtonodeEntry>, // 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 // 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<AbsoluteInputConnector, usize>, value_protonode_indices: &mut HashMap<AbsoluteInputConnector, usize>,
// 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<ProtonodePath, usize>, protonode_indices: &mut HashMap<ProtonodePath, usize>,
// The original location of the current traversal // The original location of the current traversal
traversal_input: AbsoluteInputConnector, traversal_input: AbsoluteInputConnector,
@@ -827,12 +761,11 @@ impl NodeNetwork {
Some((upstream_node_path.clone(), input_index)), 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 { let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs {
identifier, identifier,
inputs: vec![None; number_of_inputs], inputs: vec![None; number_of_inputs],
context_dependencies, context_dependencies,
node_paths: Vec::new(),
}); });
let protonode = ProtoNode { let protonode = ProtoNode {
construction_args, construction_args,
@@ -840,11 +773,11 @@ impl NodeNetwork {
input: concrete!(Context), input: concrete!(Context),
stable_node_id: NodeId(0), stable_node_id: NodeId(0),
callers: Vec::new(), callers: Vec::new(),
caller: None, original_location: OriginalLocation::Node(upstream_node_path.clone()),
}; };
let new_protonode_index = protonetwork.len(); let new_protonode_index = protonetwork.len();
protonetwork.push(ProtonodeEntry::Protonode(protonode)); 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 { let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else {
panic!("Inserted protonode must exist at new_protonode_index"); panic!("Inserted protonode must exist at new_protonode_index");
}; };
@@ -855,10 +788,6 @@ impl NodeNetwork {
if let Some(traversal_start) = traversal_start { if let Some(traversal_start) = traversal_start {
reached_protonode.callers.push(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!(), DocumentNodeImplementation::Extract => todo!(),
} }
@@ -876,18 +805,15 @@ impl NodeNetwork {
// Insert the protonode and traverse over inputs // Insert the protonode and traverse over inputs
None => { None => {
let value_protonode = ProtoNode { let value_protonode = ProtoNode {
construction_args: ConstructionArgs::Value(NodeValueArgs { construction_args: ConstructionArgs::Value(std::mem::replace(tagged_value, TaggedValue::None.into())),
value: std::mem::replace(tagged_value, TaggedValue::None.into()),
connector_paths: Vec::new(),
}),
input: concrete!(Context), // Could be () input: concrete!(Context), // Could be ()
stable_node_id: NodeId(0), stable_node_id: NodeId(0),
callers: Vec::new(), callers: Vec::new(),
caller: None, original_location: OriginalLocation::Value(traversal_input.clone()),
}; };
let new_protonode_index = protonetwork.len(); let new_protonode_index = protonetwork.len();
protonetwork.push(ProtonodeEntry::Protonode(value_protonode)); 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 { let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else {
panic!("Previously inserted protonode must exist at mapped protonode index"); panic!("Previously inserted protonode must exist at mapped protonode index");
@@ -895,15 +821,10 @@ impl NodeNetwork {
protonode protonode
} }
}; };
// Only add the traversal start if it is not the root export // Only add the traversal start if it is not the root export
if let Some(traversal_start) = traversal_start { if let Some(traversal_start) = traversal_start {
reached_protonode.callers.push(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 // Continue traversal
NodeInput::Network { import_index, .. } => { NodeInput::Network { import_index, .. } => {
@@ -969,17 +890,15 @@ impl NodeNetwork {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ProtonodeEntry { pub enum ProtonodeEntry {
Protonode(ProtoNode), Protonode(ProtoNode),
// If deduplicated, then any upstream node which this node previously called needs to map to the new protonode // A node is deduplicated if it has the same stable node id,
Deduplicated(usize), Deduplicated,
} }
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CompilationMetadata { pub struct CompilationMetadata {
// Stored for every value input in the compiled network // Stored for every value input in the compiled network
pub protonode_caller_for_values: Vec<(Vec<AbsoluteInputConnector>, CompiledProtonodeInput)>, pub original_locations: Vec<(OriginalLocation, SNI)>,
// Stored for every protonode in the compiled network pub types_to_add: Vec<(SNI, NodeIOTypes)>,
pub protonode_caller_for_nodes: Vec<(Vec<ProtonodePath>, CompiledProtonodeInput)>, pub types_to_remove: Vec<SNI>,
pub types_to_add: Vec<(SNI, Vec<Type>)>,
pub types_to_remove: Vec<(SNI, usize)>,
} }
//An Input connector with a node path for unique identification //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 { // mod test {
// use super::*; // use super::*;
// use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; // use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
@@ -1372,65 +1291,65 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
// } // }
// } // }
// fn two_node_identity() -> NodeNetwork { // fn two_node_identity() -> NodeNetwork {
// NodeNetwork { // NodeNetwork {
// exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], // exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)],
// nodes: [ // nodes: [
// ( // (
// NodeId(1), // NodeId(1),
// DocumentNode { // DocumentNode {
// inputs: vec![NodeInput::network(concrete!(u32), 0)], // inputs: vec![NodeInput::network(concrete!(u32), 0)],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default() // ..Default::default()
// }, // },
// ), // ),
// ( // (
// NodeId(2), // NodeId(2),
// DocumentNode { // DocumentNode {
// inputs: vec![NodeInput::network(concrete!(u32), 1)], // inputs: vec![NodeInput::network(concrete!(u32), 1)],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default() // ..Default::default()
// }, // },
// ), // ),
// ] // ]
// .into_iter() // .into_iter()
// .collect(), // .collect(),
// ..Default::default() // ..Default::default()
// } // }
// } // }
// fn output_duplicate(network_outputs: Vec<NodeInput>, result_node_input: NodeInput) -> NodeNetwork { // fn output_duplicate(network_outputs: Vec<NodeInput>, result_node_input: NodeInput) -> NodeNetwork {
// let mut network = NodeNetwork { // let mut network = NodeNetwork {
// exports: network_outputs, // exports: network_outputs,
// nodes: [ // nodes: [
// ( // (
// NodeId(1), // NodeId(1),
// DocumentNode { // DocumentNode {
// inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)], // inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)],
// implementation: DocumentNodeImplementation::Network(two_node_identity()), // implementation: DocumentNodeImplementation::Network(two_node_identity()),
// ..Default::default() // ..Default::default()
// }, // },
// ), // ),
// ( // (
// NodeId(2), // NodeId(2),
// DocumentNode { // DocumentNode {
// inputs: vec![result_node_input], // inputs: vec![result_node_input],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default() // ..Default::default()
// }, // },
// ), // ),
// ] // ]
// .into_iter() // .into_iter()
// .collect(), // .collect(),
// ..Default::default() // ..Default::default()
// }; // };
// let _new_ids = 101..; // let _new_ids = 101..;
// network.populate_dependants(); // 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(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.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.remove_dead_nodes(0);
// network // network
// } // }
// #[test] // #[test]
// fn simple_duplicate() { // fn simple_duplicate() {
+68 -55
View File
@@ -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::*; pub use graphene_core::registry::*;
use graphene_core::uuid::{NodeId, ProtonodePath, SNI}; use graphene_core::uuid::{NodeId, ProtonodePath, SNI};
use graphene_core::*; use graphene_core::*;
@@ -22,11 +23,8 @@ impl ProtoNetwork {
let last_entry = nodes.last().expect("Cannot compile empty protonetwork"); let last_entry = nodes.last().expect("Cannot compile empty protonetwork");
let output = match last_entry { let output = match last_entry {
ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id, ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id,
ProtonodeEntry::Deduplicated(deduplicated_index) => { ProtonodeEntry::Deduplicated => {
let ProtonodeEntry::Protonode(protonode) = &nodes[*deduplicated_index] else { panic!("Not possible for the output protonode to be deduplicated");
panic!("Deduplicated protonode must point to valid protonode");
};
protonode.stable_node_id
} }
}; };
ProtoNetwork { nodes, output } ProtoNetwork { nodes, output }
@@ -97,9 +95,10 @@ impl ProtoNetwork {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct UpstreamInputMetadata { pub struct UpstreamInputMetadata {
pub input_sni: SNI, pub input_sni: SNI,
// Context dependencies are accumulated during compilation, then replaced with whatever needs to be nullified // Context dependencies are accumulated during compilation, then replaced with the difference between the node's dependencies and the inputs dependencies
// If None, then the upstream node is a value node, so replace with an empty vec pub context_dependencies: ContextDependencies,
pub context_dependencies: Option<Vec<ContextDependency>>, // 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)] #[derive(Debug, Clone)]
@@ -112,24 +111,14 @@ pub struct NodeConstructionArgs {
// Starts as None, and is populated during stable node id generation // Starts as None, and is populated during stable node id generation
pub inputs: Vec<Option<UpstreamInputMetadata>>, pub inputs: Vec<Option<UpstreamInputMetadata>>,
// The union of all input context dependencies and the nodes context dependency. Used to generate the context nullification for the editor entry point // 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<ContextDependency>, pub context_dependencies: ContextDependencies,
// Stores the path of document nodes which correspond to it
pub node_paths: Vec<ProtonodePath>,
}
#[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<value::TaggedValue>,
// Stores all absolute input connectors which correspond to this value.
pub connector_paths: Vec<AbsoluteInputConnector>,
} }
#[derive(Debug, Clone)] #[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. /// 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 { pub enum ConstructionArgs {
Value(NodeValueArgs), /// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)
Value(MemoHash<TaggedValue>),
Nodes(NodeConstructionArgs), Nodes(NodeConstructionArgs),
/// Used for GPU computation to work around the limitations of rust-gpu. /// Used for GPU computation to work around the limitations of rust-gpu.
Inline(InlineRust), 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 // 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 struct ProtoNode {
pub construction_args: ConstructionArgs, pub construction_args: ConstructionArgs,
pub original_location: OriginalLocation,
pub input: Type, pub input: Type,
pub stable_node_id: SNI, 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)>, 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 { impl Default for ProtoNode {
fn default() -> Self { fn default() -> Self {
Self { Self {
construction_args: ConstructionArgs::Value(NodeValueArgs { construction_args: ConstructionArgs::Value(TaggedValue::U32(0).into()),
value: value::TaggedValue::U32(0).into(),
connector_paths: Vec::new(),
}),
input: concrete!(Context), input: concrete!(Context),
stable_node_id: NodeId(0), stable_node_id: NodeId(0),
callers: Vec::new(), callers: Vec::new(),
caller: None, original_location: OriginalLocation::Node(Vec::new()),
} }
} }
} }
@@ -183,7 +175,10 @@ impl ProtoNode {
input: concrete!(Context), input: concrete!(Context),
stable_node_id, stable_node_id,
callers: Vec::new(), 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); nodes.identifier.hash(&mut hasher);
} }
ConstructionArgs::Value(value) => value.value.hash(&mut hasher), ConstructionArgs::Value(value) => value.hash(&mut hasher),
ConstructionArgs::Inline(_) => todo!(), ConstructionArgs::Inline(_) => todo!(),
} }
@@ -214,6 +209,7 @@ pub enum GraphErrorType {
NoConstructor, NoConstructor,
InvalidImplementations { inputs: String, error_inputs: Vec<Vec<(usize, (Type, Type))>> }, InvalidImplementations { inputs: String, error_inputs: Vec<Vec<(usize, (Type, Type))>> },
MultipleImplementations { inputs: String, valid: Vec<NodeIOTypes> }, MultipleImplementations { inputs: String, valid: Vec<NodeIOTypes> },
UnresolvedType,
} }
impl Debug for GraphErrorType { impl Debug for GraphErrorType {
// TODO: format with the document graph context so the input index is the same as in the graph UI. // 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::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)] #[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GraphError { pub struct GraphError {
pub stable_node_id: SNI, pub original_location: OriginalLocation,
pub identifier: Cow<'static, str>, pub identifier: Cow<'static, str>,
pub error: GraphErrorType, pub error: GraphErrorType,
} }
impl GraphError { impl GraphError {
pub fn new(node: &ProtoNode, text: impl Into<GraphErrorType>) -> Self { pub fn new(construction_args: &ConstructionArgs, original_location: OriginalLocation, text: impl Into<GraphErrorType>) -> Self {
let identifier = match &node.construction_args { let identifier = match &construction_args {
ConstructionArgs::Nodes(node_construction_args) => node_construction_args.identifier.name.clone(), ConstructionArgs::Nodes(node_construction_args) => node_construction_args.identifier.name.clone(),
// Values are inserted into upcast nodes // 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(), ConstructionArgs::Inline(_) => "Inline".into(),
}; };
Self { Self {
stable_node_id: node.stable_node_id, original_location,
identifier, identifier,
error: text.into(), error: text.into(),
} }
@@ -339,10 +337,10 @@ impl TypingContext {
} }
/// Returns the inferred types for a given node id. /// Returns the inferred types for a given node id.
pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<NodeIOTypes, GraphErrors> { pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<(), GraphErrors> {
// Return the inferred type if it is already known // Return the inferred type if it is already known
if let Some(inferred) = self.inferred.get(&node_id) { if self.inferred.contains_key(&node_id) {
return Ok(inferred.clone()); return Ok(());
} }
let (inputs, id) = match node.construction_args { let (inputs, id) = match node.construction_args {
@@ -350,9 +348,9 @@ impl TypingContext {
ConstructionArgs::Value(ref v) => { ConstructionArgs::Value(ref v) => {
// assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context))); // 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 // TODO: This should return a reference to the value
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.value.ty())), vec![]); let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]);
self.inferred.insert(node_id, types.clone()); self.inferred.insert(node_id, types);
return Ok(types); return Ok(());
} }
// If the node has nodes as inputs we can infer the types from the node outputs // If the node has nodes as inputs we can infer the types from the node outputs
ConstructionArgs::Nodes(ref construction_args) => { ConstructionArgs::Nodes(ref construction_args) => {
@@ -363,7 +361,7 @@ impl TypingContext {
.map(|id| { .map(|id| {
self.inferred self.inferred
.get(&id) .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()) .map(|node| node.ty())
}) })
.collect::<Result<Vec<Type>, GraphErrors>>()?; .collect::<Result<Vec<Type>, GraphErrors>>()?;
@@ -372,18 +370,24 @@ impl TypingContext {
ConstructionArgs::Inline(ref inline) => (vec![inline.ty.clone()], &*Box::new(ProtoNodeIdentifier::new("Extract"))), 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| { if let Some(index) = inputs.iter().position(|p| {
matches!(p, matches!(p,
Type::Fn(_, b) if matches!(b.as_ref(), Type::Generic(_))) 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. /// 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. /// `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) { match (from, to) {
// Direct comparison of two concrete types. // Direct comparison of two concrete types.
(Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2, (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}")}) .map(|(i, t)| {let input_number = i + 1; format!("• Input {input_number}: {t}")})
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .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)] => { [(node_io, org_nio)] => {
let node_io = node_io.clone();
// Save the inferred type // Save the inferred type
self.inferred.insert(node_id, node_io.clone()); self.inferred.insert(node_id, node_io.clone());
self.constructor.insert(node_id, impls[org_nio]); 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 // If two types are available and one of them accepts () an input, always choose that one
[first, second] => { [first, second] => {
@@ -485,18 +491,25 @@ impl TypingContext {
// Save the inferred type // Save the inferred type
self.inferred.insert(node_id, node_io.clone()); self.inferred.insert(node_id, node_io.clone());
self.constructor.insert(node_id, impls[orig_nio]); 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::<Vec<_>>().join(", "); let inputs = [&node.input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect(); 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::<Vec<_>>().join(", "); let inputs = [&node.input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect(); 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 },
)])
} }
} }
} }
+39 -37
View File
@@ -3,7 +3,7 @@ use glam::DAffine2;
pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode}; pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer}; use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer};
use graphene_core::Context; use graphene_core::Context;
use graphene_core::ContextDependency; use graphene_core::ContextDependencies;
use graphene_core::NodeIO; use graphene_core::NodeIO;
use graphene_core::OwnedContextImpl; use graphene_core::OwnedContextImpl;
use graphene_core::WasmNotSend; use graphene_core::WasmNotSend;
@@ -52,25 +52,25 @@ pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> Do
DowncastBothNode::new(n) DowncastBothNode::new(n)
} }
pub struct EditorContextToContext { // pub struct EditorContextToContext {
first: SharedNodeContainer, // first: SharedNodeContainer,
} // }
impl<'i> Node<'i, Any<'i>> for EditorContextToContext { // impl<'i> Node<'i, Any<'i>> for EditorContextToContext {
type Output = DynFuture<'i, Any<'i>>; // type Output = DynFuture<'i, Any<'i>>;
fn eval(&'i self, input: Any<'i>) -> Self::Output { // fn eval(&'i self, input: Any<'i>) -> Self::Output {
Box::pin(async move { // Box::pin(async move {
let editor_context = dyn_any::downcast::<EditorContext>(input).unwrap(); // let editor_context = dyn_any::downcast::<EditorContext>(input).unwrap();
self.first.eval(Box::new(editor_context.to_context())).await // self.first.eval(Box::new(editor_context.to_context())).await
}) // })
} // }
} // }
impl EditorContextToContext { // impl EditorContextToContext {
pub const fn new(first: SharedNodeContainer) -> Self { // pub const fn new(first: SharedNodeContainer) -> Self {
EditorContextToContext { first } // EditorContextToContext { first }
} // }
} // }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct EditorContext { pub struct EditorContext {
@@ -101,7 +101,7 @@ unsafe impl StaticType for EditorContext {
// } // }
impl EditorContext { impl EditorContext {
pub fn to_context(&self) -> Context { pub fn to_owned_context(&self) -> OwnedContextImpl {
let mut context = OwnedContextImpl::default(); let mut context = OwnedContextImpl::default();
if let Some(footprint) = self.footprint { if let Some(footprint) = self.footprint {
context.set_footprint(footprint); context.set_footprint(footprint);
@@ -121,42 +121,44 @@ impl EditorContext {
if let Some(index) = self.index { if let Some(index) = self.index {
context.set_index(index); context.set_index(index);
} }
context
// if let Some(editor_var_args) = self.editor_var_args { // if let Some(editor_var_args) = self.editor_var_args {
// let (variable_names, values) // let (variable_names, values)
// context.set_varargs((variable_names, values)) // context.set_varargs((variable_names, values))
// } // }
context.into_context()
} }
} }
pub struct NullificationNode { pub struct NullificationNode {
first: SharedNodeContainer, first: SharedNodeContainer,
nullify: Vec<ContextDependency>, nullify: ContextDependencies,
} }
impl<'i> Node<'i, Any<'i>> for NullificationNode { impl<'i> Node<'i, Any<'i>> for NullificationNode {
type Output = DynFuture<'i, Any<'i>>; type Output = DynFuture<'i, Any<'i>>;
fn eval(&'i self, input: Any<'i>) -> Self::Output { fn eval(&'i self, input: Any<'i>) -> Self::Output {
let new_input = match dyn_any::try_downcast::<Context>(input) { Box::pin(async move {
Ok(context) => match *context { let new_input = match dyn_any::try_downcast::<Context>(input) {
Some(context) => { Ok(context) => match *context {
let mut new_context = OwnedContextImpl::from(context); Some(context) => {
new_context.nullify(&self.nullify); let mut new_context: OwnedContextImpl = OwnedContextImpl::from(context);
Box::new(new_context.into_context()) as Any<'i> new_context.nullify(&self.nullify);
} Box::new(new_context.into_context()) as Any<'i>
None => { }
let none: Context = None; None => {
Box::new(none) as Any<'i> let none: Context = None;
} Box::new(none) as Any<'i>
}, }
Err(other_input) => other_input, },
}; Err(other_input) => other_input,
Box::pin(async move { self.first.eval(new_input).await }) };
self.first.eval(new_input).await
})
} }
} }
impl NullificationNode { impl NullificationNode {
pub fn new(first: SharedNodeContainer, nullify: Vec<ContextDependency>) -> Self { pub fn new(first: SharedNodeContainer, nullify: ContextDependencies) -> Self {
Self { first, nullify } Self { first, nullify }
} }
} }
@@ -1,13 +1,12 @@
use crate::node_registry::{CACHE_NODES, NODE_REGISTRY}; 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::document::value::{TaggedValue, UpcastNode};
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, UpstreamInputMetadata}; use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, UpstreamInputMetadata};
use graph_craft::proto::{GraphErrorType, GraphErrors}; use graph_craft::proto::{GraphErrorType, GraphErrors};
use graph_craft::{Type, concrete}; use graph_craft::{Type, concrete};
use graphene_std::Context; use graphene_std::any::{EditorContext, NullificationNode};
use graphene_std::any::{EditorContext, EditorContextToContext, NullificationNode}; use graphene_std::uuid::{NodeId, SNI};
use graphene_std::memo::IntrospectMode; use graphene_std::{Context, ContextDependencies, NodeIOTypes};
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::error::Error; use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
@@ -46,18 +45,13 @@ impl DynamicExecutor {
/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible. /// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.
#[cfg_attr(debug_assertions, inline(never))] #[cfg_attr(debug_assertions, inline(never))]
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), GraphErrors> { pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, NodeIOTypes)>, Vec<SNI>), GraphErrors> {
self.output = Some(proto_network.output); self.output = Some(proto_network.output);
self.typing_context.update(&proto_network)?; 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 (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?;
let mut remove = Vec::new(); let mut remove = Vec::new();
for sni in orphaned_proto_nodes { for sni in orphaned_proto_nodes {
if let Some(number_of_inputs) = self.tree.free_node(&sni) { remove.push(sni);
remove.push((sni, number_of_inputs));
}
self.typing_context.remove_inference(&sni); self.typing_context.remove_inference(&sni);
} }
@@ -65,37 +59,20 @@ impl DynamicExecutor {
.into_iter() .into_iter()
.filter_map(|sni| { .filter_map(|sni| {
let Some(types) = self.typing_context.type_of(sni) else { let Some(types) = self.typing_context.type_of(sni) else {
log::error!("Could not get type for sni: {:?}", sni);
return None; return None;
}; };
Some((sni, types.inputs.clone())) Some((sni, types.clone()))
}) })
.collect(); .collect();
Ok((add_with_types, remove)) Ok((add_with_types, remove))
} }
/// Intospect the value for that specific protonode input, returning for example the cached value for a monitor node. // Introspect the cached output of any protonode
pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result<Option<Arc<dyn std::any::Any + Send + Sync>>, IntrospectError> { pub fn introspect(&self, protonode: SNI, check_if_evaluated: bool) -> Result<Option<Arc<dyn std::any::Any + Send + Sync>>, IntrospectError> {
let node = self.get_introspect_node_container(protonode_input)?; let inserted_node = self.tree.nodes.get(&protonode).ok_or(IntrospectError::ProtoNodeNotFound(protonode))?;
Ok(node.introspect(introspect_mode)) Ok(inserted_node.cached_protonode.introspect(check_if_evaluated))
}
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<SharedNodeContainer, IntrospectError> {
// 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())
} }
pub fn input_type(&self) -> Option<Type> { pub fn input_type(&self) -> Option<Type> {
@@ -124,6 +101,7 @@ impl DynamicExecutor {
.type_of(node_to_evaluate) .type_of(node_to_evaluate)
.map(|node_io| node_io.call_argument.clone()) .map(|node_io| node_io.call_argument.clone())
.ok_or("Could not get input type of network to execute".to_string())?; .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 // A node to convert the EditorContext to the Context is automatically inserted for each node at id-1
let result = match input_type { let result = match input_type {
t if t == concrete!(Context) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()), 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)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IntrospectError { pub enum IntrospectError {
PathNotFound(Vec<NodeId>), PathNotFound(Vec<NodeId>),
ProtoNodeNotFound(CompiledProtonodeInput), ProtoNodeNotFound(SNI),
InputIndexOutOfBounds(CompiledProtonodeInput), // InvalidInputType(SNI),
InvalidInputType(CompiledProtonodeInput),
NoData, NoData,
RuntimeNotReady, RuntimeNotReady,
IntrospectNotImplemented, IntrospectNotImplemented,
@@ -174,31 +151,21 @@ impl std::fmt::Display for IntrospectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
IntrospectError::PathNotFound(path) => write!(f, "Path not found: {:?}", path), 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::NoData => write!(f, "No data found for this node"),
IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"), IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"),
IntrospectError::IntrospectNotImplemented => write!(f, "Intospect not implemented"), 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)] #[derive(Clone)]
struct InsertedProtonode { struct InsertedProtonode {
// If the inserted protonode is a value node, then do not clear types when removing // Either the value node, cache node if output is clone, or protonode if output is not clone
is_value: bool,
// Either the value node, cache node, or protonode if output is not clone
cached_protonode: SharedNodeContainer, cached_protonode: SharedNodeContainer,
// Value nodes are the entry points, since they can be directly evaluated // A list of arguments in the context to nullify when executing the node
// Nodes with cloneable outputs have a cache, then editor entry point nullify_when_calling: ContextDependencies,
// 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<SharedNodeContainer>,
// They also store references to the upstream cache/value node, used for introspection
input_introspection_entrypoints: Vec<SharedNodeContainer>,
} }
/// A store of dynamically typed nodes and their associated source map. /// A store of dynamically typed nodes and their associated source map.
@@ -243,10 +210,7 @@ impl BorrowTree {
let sni = node.stable_node_id; let sni = node.stable_node_id;
old_nodes.remove(&sni); old_nodes.remove(&sni);
if !self.nodes.contains_key(&sni) { if !self.nodes.contains_key(&sni) {
// Do not send types for auto inserted value nodes nodes_with_new_type.push(sni);
if matches!(node.construction_args, ConstructionArgs::Nodes(_)) {
nodes_with_new_type.push(sni)
}
self.push_node(node, typing_context).await?; self.push_node(node, typing_context).await?;
} }
} }
@@ -262,23 +226,35 @@ impl BorrowTree {
} }
/// Evaluate any node in the borrow tree /// Evaluate any node in the borrow tree
pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option<O> // pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option<O>
where // where
I: StaticType + 'i + Send + Sync, // I: StaticType + 'i + Send + Sync,
O: StaticType + 'i, // O: StaticType + 'i,
{ // {
let node = self.nodes.get(&id)?; // let node = self.nodes.get(&id)?;
let output = node.output_editor_entrypoint.eval(Box::new(input)); // let output = node.output_editor_entrypoint.eval(Box::new(input));
dyn_any::downcast::<O>(output.await).ok().map(|o| *o) // dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
} // }
/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value. /// 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. /// This ensures that no borrowed data can escape the node graph.
pub async fn eval_tagged_value<I>(&self, id: SNI, input: I) -> Result<TaggedValue, String> pub async fn eval_tagged_value<'i, I>(&'i self, id: SNI, input: I) -> Result<TaggedValue, String>
where where
I: StaticType + 'static + Send + Sync, I: StaticType + 'static + Send + Sync,
{ {
let inserted_node = self.nodes.get(&id).ok_or("Output node not found in executor")?; 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::<EditorContext>(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) TaggedValue::try_from_any(output.await)
} }
@@ -337,9 +313,8 @@ impl BorrowTree {
/// - Removes the node from `nodes` HashMap. /// - 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. /// - 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. /// - Returns `None` if the node is not found in the `nodes` HashMap.
pub fn free_node(&mut self, id: &SNI) -> Option<usize> { pub fn free_node(&mut self, id: &SNI) {
let removed_node = self.nodes.remove(&id).expect(&format!("Could not remove node: {:?}", id)); self.nodes.remove(&id).expect("Node could not be removed");
removed_node.is_value.then_some(removed_node.input_thumbnail_entrypoints.len())
} }
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`. /// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
@@ -360,31 +335,37 @@ impl BorrowTree {
/// Thumbnails is a mapping of the protonode input to the rendered thumbnail through the monitor cache node /// 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> { async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> {
let sni = proto_node.stable_node_id; let sni = proto_node.stable_node_id;
// Move the value into the upcast node instead of cloning it
match proto_node.construction_args { match proto_node.construction_args {
ConstructionArgs::Value(value_args) => { ConstructionArgs::Value(value) => {
let upcasted = UpcastNode::new(value_args.value); let upcasted = UpcastNode::new(value);
let node = Box::new(upcasted) as TypeErasedBox<'_>; let node = Box::new(upcasted) as TypeErasedBox<'_>;
let value_node = NodeContainer::new(node); let cached_protonode = NodeContainer::new(node);
let inserted_protonode = InsertedProtonode { let inserted_protonode = InsertedProtonode {
is_value: true, cached_protonode,
cached_protonode: value_node.clone(), nullify_when_calling: ContextDependencies::none(),
output_editor_entrypoint: value_node,
input_thumbnail_entrypoints: Vec::new(),
input_introspection_entrypoints: Vec::new(),
}; };
self.nodes.insert(sni, inserted_protonode); self.nodes.insert(sni, inserted_protonode);
} }
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(node_construction_args) => { 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 let Some(constructor) = typing_context.constructor(sni) else {
.iter() return Err(vec![GraphError::new(
.map(|inserted_protonode| inserted_protonode.output_editor_entrypoint.clone()) &ConstructionArgs::Nodes(node_construction_args),
.collect::<Vec<_>>(); proto_node.original_location,
let input_introspection_entrypoints = construction_nodes.iter().map(|inserted_protonode| inserted_protonode.cached_protonode.clone()).collect::<Vec<_>>(); GraphErrorType::NoConstructor,
)]);
};
let construction_nodes = self.node_deps(&node_construction_args.inputs);
// Insert nullification if necessary // Insert nullification if necessary
let protonode_inputs = construction_nodes let protonode_inputs = construction_nodes
@@ -392,36 +373,20 @@ impl BorrowTree {
.zip(node_construction_args.inputs.into_iter()) .zip(node_construction_args.inputs.into_iter())
.map(|(inserted_protonode, input_metadata)| { .map(|(inserted_protonode, input_metadata)| {
let previous_input = inserted_protonode.cached_protonode.clone(); let previous_input = inserted_protonode.cached_protonode.clone();
let input_context_dependencies = input_metadata.unwrap().context_dependencies.unwrap(); let input_context_dependencies = input_metadata.unwrap().context_dependencies;
let protonode_input = if !input_context_dependencies.is_empty() { if !input_context_dependencies.is_empty() {
let nullification_node = NullificationNode::new(previous_input, input_context_dependencies); let nullification_node = NullificationNode::new(previous_input, input_context_dependencies);
let node = Box::new(nullification_node) as TypeErasedBox<'_>; let node = Box::new(nullification_node) as TypeErasedBox<'_>;
NodeContainer::new(node) NodeContainer::new(node)
} else { } else {
previous_input previous_input
}; }
protonode_input
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
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 node = constructor(protonode_inputs).await;
let protonode = NodeContainer::new(node); 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 // 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 cached_protonode = if let Some(cache_constructor) = typing_context.cache_constructor(&types.return_value.nested_type()) {
let cache = cache_constructor(protonode); let cache = cache_constructor(protonode);
@@ -431,36 +396,17 @@ impl BorrowTree {
protonode protonode
}; };
// If the call argument is Context, insert a conversion node between EditorContext to Context so that it can be evaluated // When evaluating the node from the editor, nullify all context fields it is not dependent on
// Also insert the nullification node to whatever the protonode is not dependent on let nullify_when_calling = node_construction_args.context_dependencies.inverse();
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::<Vec<_>>();
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);
let inserted_protonode = InsertedProtonode { let inserted_protonode = InsertedProtonode {
is_value: false,
cached_protonode, cached_protonode,
output_editor_entrypoint, nullify_when_calling,
input_thumbnail_entrypoints,
input_introspection_entrypoints,
}; };
self.nodes.insert(sni, inserted_protonode); self.nodes.insert(sni, inserted_protonode);
} }
}; }
Ok(()) Ok(())
} }
} }
@@ -476,7 +422,7 @@ mod test {
let mut tree = BorrowTree::default(); let mut tree = BorrowTree::default();
let val_1_protonode = ProtoNode::value( let val_1_protonode = ProtoNode::value(
ConstructionArgs::Value(NodeValueArgs { ConstructionArgs::Value(NodeValueArgs {
value: TaggedValue::U32(2u32).into(), value: Some(TaggedValue::U32(2u32).into()),
connector_paths: Vec::new(), connector_paths: Vec::new(),
}), }),
NodeId(0), NodeId(0),
@@ -485,7 +431,7 @@ mod test {
let future = tree.push_node(val_1_protonode, &context); let future = tree.push_node(val_1_protonode, &context);
futures::executor::block_on(future).unwrap(); futures::executor::block_on(future).unwrap();
let _node = tree.nodes.get(&NodeId(0)).expect("Node should be added to tree"); let _node = tree.nodes.get(&NodeId(0)).expect("Node should be added to tree");
let result = futures::executor::block_on(tree.eval(NodeId(0), ())); let result = futures::executor::block_on(tree.eval_tagged_value(NodeId(0), ()));
assert_eq!(result, Some(2u32)); assert_eq!(result, Some(TaggedValue::U32(2u32).into()));
} }
} }
+4 -4
View File
@@ -375,7 +375,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
mod #mod_name { mod #mod_name {
use super::*; use super::*;
use #graphene_core as gcore; use #graphene_core as gcore;
use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO, ContextDependency}; use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO, ContextDependency, ContextDependencies};
use gcore::value::ClonedNode; use gcore::value::ClonedNode;
use gcore::ops::TypeNode; use gcore::ops::TypeNode;
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, NODE_CONTEXT_DEPENDENCY, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride}; use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, NODE_CONTEXT_DEPENDENCY, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
@@ -436,10 +436,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
fn register_context_dependency() { fn register_context_dependency() {
let mut context_dependency = NODE_CONTEXT_DEPENDENCY.lock().unwrap(); let mut context_dependency = NODE_CONTEXT_DEPENDENCY.lock().unwrap();
context_dependency.insert( context_dependency.insert(
#identifier, #identifier().to_string(),
vec![ ContextDependencies::from(vec![
#(ContextDependency::#context_dependencies,)* #(ContextDependency::#context_dependencies,)*
] ])
); );
} }
} }