diff --git a/document/graph-storage/src/attributes/attr.rs b/document/graph-storage/src/attributes/attr.rs index 3fde1be0f1..749672a83e 100644 --- a/document/graph-storage/src/attributes/attr.rs +++ b/document/graph-storage/src/attributes/attr.rs @@ -1,6 +1,5 @@ pub mod node { pub const CALL_ARGUMENT: &str = "call_argument"; - pub const CONTEXT_FEATURES: &str = "context_features"; pub const VISIBLE: &str = "visible"; pub const SKIP_DEDUPLICATION: &str = "skip_deduplication"; pub const REFLECTION_METADATA: &str = "reflection_metadata"; diff --git a/document/graph-storage/src/from_runtime.rs b/document/graph-storage/src/from_runtime.rs index 110f60200c..475ec63d74 100644 --- a/document/graph-storage/src/from_runtime.rs +++ b/document/graph-storage/src/from_runtime.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use core_types::Context; -use core_types::context::ContextDependencies; use core_types::uuid::NodeId as RuntimeNodeId; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; @@ -390,9 +389,6 @@ fn convert_node(doc_node: &DocumentNode, locatio attributes .set_if_not_default(node::CALL_ARGUMENT, &doc_node.call_argument, &concrete!(Context), timestamp) .map_err(map_serialization_error(node::CALL_ARGUMENT))?; - attributes - .set_if_not_default(node::CONTEXT_FEATURES, &doc_node.context_features, &ContextDependencies::default(), timestamp) - .map_err(map_serialization_error(node::CONTEXT_FEATURES))?; attributes .set_if_not_default(node::VISIBLE, &doc_node.visible, &true, timestamp) .map_err(map_serialization_error(node::VISIBLE))?; diff --git a/document/graph-storage/src/tests/round_trip.rs b/document/graph-storage/src/tests/round_trip.rs index 168c7d85e4..58ef746b9c 100644 --- a/document/graph-storage/src/tests/round_trip.rs +++ b/document/graph-storage/src/tests/round_trip.rs @@ -248,11 +248,6 @@ fn test_nested_network_flattening() { #[test] fn test_metadata_preservation() { // Create a network with nodes that have non-default metadata - let context_features = ContextDependencies::new( - core_types::context::ContextFeatures::FOOTPRINT | core_types::context::ContextFeatures::REAL_TIME, - core_types::context::ContextFeatures::empty(), - ); - let network = NodeNetwork { exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ @@ -262,7 +257,6 @@ fn test_metadata_preservation() { inputs: vec![NodeInput::import(concrete!(f64), 0), NodeInput::import(Type::Generic(Cow::Borrowed("T")), 1)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("test::NodeWithMetadata")), call_argument: concrete!(String), - context_features, visible: false, // Non-default value skip_deduplication: true, // Non-default value ..Default::default() @@ -296,8 +290,12 @@ fn test_metadata_preservation() { let conv_node_1 = converted.nodes.get(&NodeId(1)).unwrap(); assert_eq!(orig_node_1.call_argument, conv_node_1.call_argument, "call_argument for node 1 should be preserved"); - // Verify context_features is preserved - assert_eq!(orig_node_0.context_features, conv_node_0.context_features, "context_features should be preserved"); + // Verify context_features is not stored + assert_eq!( + conv_node_0.context_features, + ContextDependencies::default(), + "context_features should resolve at compile, not round-trip" + ); // Verify visible is preserved assert_eq!(orig_node_0.visible, conv_node_0.visible, "visible should be preserved"); diff --git a/document/graph-storage/src/to_runtime.rs b/document/graph-storage/src/to_runtime.rs index b566b6ccc3..fba6f71494 100644 --- a/document/graph-storage/src/to_runtime.rs +++ b/document/graph-storage/src/to_runtime.rs @@ -310,8 +310,8 @@ fn convert_node( implementation: convert_implementation(context, &node.implementation, metadata_path, runtime_node_id, node_collector, network_collector)?, visible: node.attributes.get_or(node::VISIBLE, true), skip_deduplication: node.attributes.get_or(node::SKIP_DEDUPLICATION, false), - context_features: node.attributes.get_or_default(node::CONTEXT_FEATURES), // Regenerated during compilation; not stored. + context_features: Default::default(), original_location: Default::default(), }) } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index f4f5b6fe98..94df698af2 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -42,7 +42,6 @@ pub(super) fn post_process_nodes(custom: Vec) -> HashMap fields, description, properties, - context_features, .. } = metadata; @@ -78,7 +77,7 @@ pub(super) fn post_process_nodes(custom: Vec) -> HashMap implementation: DocumentNodeImplementation::ProtoNode(id.clone()), visible: true, skip_deduplication: false, - context_features: ContextDependencies::from(context_features.as_slice()), + context_features: ContextDependencies::default(), ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 14fbcc6216..573322ce51 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -24,7 +24,6 @@ use graph_craft::Type; use graph_craft::application_io::resource::ResourceId; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; -use graphene_std::ContextDependencies; use graphene_std::Graphic; use graphene_std::list::List; use graphene_std::math::quad::Quad; @@ -4090,18 +4089,6 @@ impl NodeNetworkInterface { node.call_argument = call_argument; } - pub fn set_context_features(&mut self, node_id: &NodeId, network_path: &[NodeId], context_features: ContextDependencies) { - let Some(network) = self.network_mut(network_path) else { - log::error!("Could not get nested network in set_context_features"); - return; - }; - let Some(node) = network.nodes.get_mut(node_id) else { - log::error!("Could not get node in set_context_features"); - return; - }; - node.context_features = context_features; - } - /// Lightweight version of `set_input` for bulk import operations. /// Directly sets the input without `is_acyclic` checks, `load_structure`, position conversions, /// or per-node cache invalidation. Call `load_structure`, `unload_all_nodes_click_targets`, and diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 5e1add6c8a..e354108400 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -2441,15 +2441,6 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], } } - // Add context features to nodes that don't have them (fine-grained context caching migration) - if node.context_features == graphene_std::ContextDependencies::default() - && let Some(reference) = document.network_interface.reference(node_id, network_path).clone() - && let Some(node_definition) = resolve_document_node_type(&reference) - { - let context_features = node_definition.node_template.document_node.context_features.clone(); - document.network_interface.set_context_features(node_id, network_path, context_features); - } - // Add the "Scale Type" parameter to the "Decompose Scale" node if reference == DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::decompose_scale::IDENTIFIER) && inputs_count == 1 { let mut node_template = resolve_document_node_type(&reference)?.default_node_template(); diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index fc1dd3ad10..5995f4f839 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -57,7 +57,8 @@ pub struct DocumentNode { #[serde(default)] pub skip_deduplication: bool, /// List of Extract and Inject annotations for the Context. - #[serde(default)] + /// Resolved from the registry at compile time; only code-built wrapper nodes carry a declaration here. + #[serde(skip)] pub context_features: ContextDependencies, /// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called. #[serde(skip)] diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 469bfef719..eaa8422800 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -500,18 +500,18 @@ impl ProtoNetwork { nullification_node_id } - /// The node's declared index levels and per-input pushed levels, both read + /// The node's declared dependencies and per-input pushed levels, both read /// from the registry since they follow the node's signature. A node with no - /// registry entry pushes nothing and names no level. - fn registry_index_levels(&self, node_index: usize) -> (core_types::context::IndexLevels, Vec) { + /// registry entry declares nothing and pushes nothing. + fn registry_dependencies(&self, node_index: usize) -> (ContextDependencies, Vec) { let identifier = &self.nodes[node_index].1.identifier; let metadata = core_types::registry::NODE_METADATA.lock().unwrap(); match metadata.get(identifier) { Some(entry) => ( - ContextDependencies::from(entry.context_features.as_slice()).index_levels, + ContextDependencies::from(entry.context_features.as_slice()), entry.fields.iter().map(|field| field.pushed_levels).collect(), ), - None => (core_types::context::IndexLevels::empty(), Vec::new()), + None => (ContextDependencies::default(), Vec::new()), } } @@ -520,14 +520,21 @@ impl ProtoNetwork { let mut combined_deps = ContextModification::default(); let node_index = id.0 as usize; - let (declared_levels, pushed_levels) = self.registry_index_levels(node_index); + let (registry_deps, pushed_levels) = self.registry_dependencies(node_index); let (extract, inject, own_deps) = { - let dependencies = &self.nodes[node_index].1.context_features; + let carried = &self.nodes[node_index].1.context_features; + // A code-built wrapper node declares dependencies its signature cannot + // express; everything else resolves from the registry. Sources are + // input wiring, not declarations, so they merge either way. + let mut dependencies = match carried.extract.is_empty() && carried.inject.is_empty() { + false => carried.clone(), + true => registry_deps, + }; + dependencies.add_sources(carried.sources()); let index_levels = match dependencies.extract.contains(core_types::context::ContextFeatures::INDEX) { - // A wrapper node declaring its dependencies by hand keeps `INDEX` - // without the signature naming a level, and addresses the innermost. - true if declared_levels.is_empty() => core_types::context::IndexLevels::innermost(), - true => declared_levels, + // A hand-declared `INDEX` names no level and addresses the innermost. + true if dependencies.index_levels.is_empty() => core_types::context::IndexLevels::innermost(), + true => dependencies.index_levels, false => core_types::context::IndexLevels::empty(), }; let own_deps = ContextModification::from_sources(dependencies.extract, dependencies.sources()).with_index_levels(index_levels); @@ -536,7 +543,15 @@ impl ProtoNetwork { let mut inputs = match &self.nodes[node_index].1.construction_args { // We pretend like we have already placed context modification nodes after ourselves because value nodes don't need to be cached - ConstructionArgs::Value(_) => return (own_deps, Some(id)), + ConstructionArgs::Value(value) => { + let mut deps = own_deps; + // A leveled value serves its lanes by the innermost index. + if value.value_layout().is_some_and(|layout| layout.depth > 0) { + deps |= core_types::context::ContextFeatures::INDEX; + deps.index_levels |= core_types::context::IndexLevels::innermost(); + } + return (deps, Some(id)); + } ConstructionArgs::Nodes(items) => items.clone(), ConstructionArgs::Inline(_) => return (own_deps, Some(id)), }; @@ -571,6 +586,9 @@ impl ProtoNetwork { self.nodes[node_index].1.construction_args = ConstructionArgs::Nodes(inputs); // Which dependencies do we supply (and don't need ourselves)? + // TODO: a shrinking index-level mask does not count as supplying a + // dependency, so a fold that consumes its subtree's only level inserts + // no boundary here. let net_injections = inject.difference(extract); // Which dependencies still need to be met after this node? diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 200839afe8..33616aaff5 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -205,7 +205,6 @@ impl Preprocessor { implementation: DocumentNodeImplementation::ProtoNode(id.clone()), visible: true, skip_deduplication: false, - context_features: ContextDependencies::from(metadata.context_features.as_slice()), ..Default::default() }; @@ -256,7 +255,6 @@ impl Preprocessor { call_argument: node_io.call_argument.clone(), implementation: DocumentNodeImplementation::ProtoNode(id.clone()), visible: true, - context_features: ContextDependencies::from(metadata.context_features.as_slice()), ..Default::default() }; inject_scopes.insert(id.clone(), (template, node_io.return_value.clone()));