diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index c0228aedec8..47041e27ff9 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -167,6 +167,7 @@ impl DocumentNode { original_location: self.original_location, skip_deduplication: self.skip_deduplication, context_dependencies: self.context_dependencies.unwrap_or_default(), + upstream_context_features: Default::default(), } } } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index ce1994d1861..80b8648ef0c 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -134,6 +134,8 @@ pub struct ProtoNode { pub original_location: OriginalLocation, pub skip_deduplication: bool, pub(crate) context_dependencies: ContextDependencies, + /// The context features read by this node and everything upstream of it. + pub upstream_context_features: ContextFeatures, } impl Default for ProtoNode { @@ -145,6 +147,7 @@ impl Default for ProtoNode { original_location: OriginalLocation::default(), skip_deduplication: false, context_dependencies: Default::default(), + upstream_context_features: ContextFeatures::empty(), } } } @@ -180,6 +183,7 @@ impl ProtoNode { }, skip_deduplication: false, context_dependencies: Default::default(), + upstream_context_features: ContextFeatures::empty(), } } @@ -280,7 +284,8 @@ impl ProtoNetwork { // Perform topological sort once self.reorder_ids()?; - self.find_context_dependencies(self.output); + let (output_dependencies, _) = self.find_context_dependencies(self.output); + self.nodes[self.output.0 as usize].1.upstream_context_features = output_dependencies.features; // Perform topological sort a second time to integrate the new nodes self.reorder_ids()?; @@ -297,6 +302,7 @@ impl ProtoNetwork { p.push(NodeId(10)) } + let features = context_deps.features; let memoize_node_id = NodeId(self.nodes.len() as u64); self.nodes.push(( @@ -309,6 +315,7 @@ impl ProtoNetwork { path: path.clone(), ..Default::default() }, + upstream_context_features: features, ..Default::default() }, )); @@ -339,6 +346,7 @@ impl ProtoNetwork { path: path.clone(), ..Default::default() }, + upstream_context_features: features, ..Default::default() }, )); @@ -380,6 +388,7 @@ impl ProtoNetwork { // Compute the dependencies for each branch and combine all of them for &node in &inputs { let branch = self.find_context_dependencies(node); + self.nodes[node.0 as usize].1.upstream_context_features = branch.0.features; combined_deps.features |= branch.0.features; combined_deps.sources.extend(&branch.0.sources); @@ -1133,6 +1142,44 @@ mod test { .collect() } + #[test] + fn nodes_record_the_context_features_their_output_depends_on() { + let node = |name: &'static str, construction_args: ConstructionArgs, extract: ContextFeatures, inject: ContextFeatures| ProtoNode { + identifier: ProtoNodeIdentifier::new(name), + call_argument: concrete!(()), + construction_args, + context_dependencies: ContextDependencies { extract, inject }, + ..Default::default() + }; + let none = ContextFeatures::empty(); + + let mut network = ProtoNetwork { + inputs: vec![], + output: NodeId(3), + nodes: vec![ + (NodeId(0), node("value", ConstructionArgs::Value(TaggedValue::Integer(2).into()), none, none)), + ( + NodeId(1), + node("reader", ConstructionArgs::Nodes(vec![NodeId(0)]), ContextFeatures::ANIMATION_TIME | ContextFeatures::INDEX, none), + ), + (NodeId(2), node("repeat", ConstructionArgs::Nodes(vec![NodeId(1)]), none, ContextFeatures::INDEX)), + (NodeId(3), node("wrap", ConstructionArgs::Nodes(vec![NodeId(2)]), none, none)), + ], + }; + network.insert_context_nullification_nodes().unwrap(); + + let features_of = |name: &str| network.nodes.iter().find(|(_, node)| node.identifier.as_str() == name).unwrap().1.upstream_context_features; + assert_eq!(features_of("value"), none); + assert_eq!(features_of("reader"), ContextFeatures::ANIMATION_TIME | ContextFeatures::INDEX); + assert_eq!(features_of("repeat"), ContextFeatures::ANIMATION_TIME); + assert_eq!(features_of("wrap"), ContextFeatures::ANIMATION_TIME); + assert_eq!(features_of(graphene_core::memo::memoize::IDENTIFIER.as_str()), ContextFeatures::ANIMATION_TIME); + assert_eq!( + features_of(graphene_core::context_modification::context_modification::IDENTIFIER.as_str()), + ContextFeatures::ANIMATION_TIME + ); + } + fn source_branch_network(branch_a_sources: &[SourceId], branch_b_sources: &[SourceId]) -> ProtoNetwork { let node = |name: &'static str, construction_args: ConstructionArgs| ProtoNode { identifier: ProtoNodeIdentifier::new(name), diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index aa79de34921..fa910afdf91 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -211,6 +211,8 @@ pub struct BorrowTree { nodes: HashMap)>, /// A hashmap from the document path to the proto node ID. source_map: HashMap, + /// The context features each node's output depends on. + context_features: HashMap, } impl BorrowTree { @@ -258,6 +260,11 @@ impl BorrowTree { self.nodes.get(&id).map(|(node, _, _)| node.clone()) } + /// The context features this node's output depends on. + pub fn context_features(&self, id: NodeId) -> core_types::ContextFeatures { + self.context_features.get(&id).copied().unwrap_or_default() + } + /// Evaluate the output node of the [`BorrowTree`]. pub fn eval(&self, id: NodeId, input: &I) -> Option where @@ -343,6 +350,7 @@ impl BorrowTree { /// - If the node is the primary node for its path in the `source_map`, it's also removed from there. /// - Returns `None` if the node is not found in the `nodes` HashMap. pub fn free_node(&mut self, id: NodeId) -> Option { + self.context_features.remove(&id); let (_, path, _) = self.nodes.remove(&id)?; if self.source_map.get(&path)?.0 == id { self.source_map.remove(&path); @@ -413,6 +421,7 @@ impl BorrowTree { /// - Returns an error if no constructor is found for the given node ID. fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> { self.update_source_map(id, typing_context, &proto_node); + self.context_features.insert(id, proto_node.upstream_context_features); let path = proto_node.original_location.path.clone().unwrap_or_default(); match &proto_node.construction_args {