From f4247986f0c629270fc1ed1c9fd7f6e842c8d809 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 30 Jul 2026 15:46:10 +0000 Subject: [PATCH] Wire source ids through flattening, the nullification pass, and slot keying --- .../messages/portfolio/document_migration.rs | 2 +- node-graph/graph-craft/src/document.rs | 22 +++- node-graph/graph-craft/src/document/value.rs | 16 +-- node-graph/graph-craft/src/proto.rs | 32 +++--- .../interpreted-executor/src/node_registry.rs | 24 ++--- node-graph/interpreted-executor/src/util.rs | 6 ++ .../libraries/core-types/src/context.rs | 100 ++++++++++++++---- .../libraries/core-types/src/registry.rs | 1 + .../libraries/core-types/src/runtime.rs | 8 +- node-graph/node-macro/src/codegen.rs | 1 + node-graph/node-macro/src/gcodegen.rs | 12 ++- node-graph/node-macro/src/parsing.rs | 3 +- .../nodes/gcore/src/context_modification.rs | 8 +- node-graph/preprocessor/src/lib.rs | 1 + 14 files changed, 166 insertions(+), 70 deletions(-) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index ec9483d4a2..4bad328df5 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -2406,7 +2406,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], && 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; + let context_features = node_definition.node_template.document_node.context_features.clone(); document.network_interface.set_context_features(node_id, network_path, context_features); } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 838dc5fea3..e57665b691 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -8,7 +8,6 @@ pub use core_types::uuid::generate_uuid; use core_types::{Context, ContextDependencies, Cow, MemoHash, ProtoNodeIdentifier, Type}; use dyn_any::DynAny; use glam::IVec2; -use log::Metadata; use rustc_hash::FxHashMap; use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; @@ -215,12 +214,14 @@ impl InlineRust { #[derive(Debug, Clone, PartialEq, Hash, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)] pub enum DocumentNodeMetadata { DocumentNodePath, + SourceId, } impl DocumentNodeMetadata { pub fn ty(&self) -> Type { match self { DocumentNodeMetadata::DocumentNodePath => concrete!(core_types::list::List), + DocumentNodeMetadata::SourceId => concrete!(u64), } } } @@ -273,7 +274,7 @@ impl NodeInput { NodeInput::Import { import_type, .. } => import_type.clone(), NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"), NodeInput::Scope(_) => panic!("ty() called on NodeInput::Scope"), - NodeInput::Reflection(_) => concrete!(Metadata), + NodeInput::Reflection(metadata) => metadata.ty(), } } @@ -879,7 +880,7 @@ impl NodeNetwork { // Replace value inputs with dedicated value nodes if node.implementation != DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::value::ClonedNode")) { - Self::replace_value_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id); + Self::replace_value_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id, Some(&mut node.context_features)); } let DocumentNodeImplementation::Network(mut inner_network) = node.implementation else { @@ -898,6 +899,7 @@ impl NodeNetwork { gen_id, map_ids, id, + None, ); // Connect all network inputs to either the parent network nodes, or newly created value nodes for the parent node. @@ -978,6 +980,12 @@ impl NodeNetwork { } } + fn source_id_for_path(path: &[NodeId]) -> u64 { + let mut hasher = DefaultHasher::new(); + path.hash(&mut hasher); + hasher.finish() + } + #[inline(never)] fn replace_value_inputs_with_nodes( inputs: &mut [NodeInput], @@ -986,6 +994,7 @@ impl NodeNetwork { gen_id: impl Fn() -> NodeId + Copy, map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy, id: NodeId, + mut context_features: Option<&mut ContextDependencies>, ) { // Replace value exports and imports with value nodes, added inside the nested network for export in inputs { @@ -996,6 +1005,13 @@ impl NodeNetwork { NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed), NodeInput::Reflection(reflect) => match reflect { DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodeIdPath(path.to_vec()).into(), false), + DocumentNodeMetadata::SourceId => { + let source_id = Self::source_id_for_path(path); + if let Some(context_features) = context_features.as_deref_mut() { + core_types::context::merge_sorted_sources(&mut context_features.sources, &[source_id]); + } + (TaggedValue::U64(source_id).into(), false) + } }, previous_export => { *export = previous_export; diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 42d1574e3b..70d67e8caa 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -12,7 +12,7 @@ use core_types::gnode::GNode; use core_types::gpoll::GPoll; use core_types::registry::{EdgeHandle, edge_type}; use core_types::value::value_edge; -use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type, TypeDescriptor}; +use core_types::{CacheHash, Color, ContextModification, MemoHash, Node, Type, TypeDescriptor}; use dyn_any::DynAny; pub use dyn_any::StaticType; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; @@ -95,7 +95,7 @@ macro_rules! tagged_value { DocumentNode(DocumentNode), /// Carried by context nullification proto nodes constructed at proto node compilation time in `insert_context_nullification_nodes`. #[serde(skip)] - ContextFeatures(ContextFeatures), + ContextModification(ContextModification), #[serde(skip)] EditorApi(Arc), /// Only used by the `resource` node, should never be serialized @@ -125,7 +125,7 @@ macro_rules! tagged_value { // ======================= Self::NodeIdPath(path) => path.hash(state), Self::DocumentNode(node) => node.cache_hash(state), - Self::ContextFeatures(features) => features.cache_hash(state), + Self::ContextModification(modification) => modification.cache_hash(state), Self::RenderOutput(x) => x.cache_hash(state), Self::EditorApi(x) => x.cache_hash(state), Self::ResourceHash(x) => x.cache_hash(state), @@ -180,7 +180,7 @@ macro_rules! tagged_value { Box::new(list) } Self::DocumentNode(node) => Box::new(node), - Self::ContextFeatures(features) => Box::new(features), + Self::ContextModification(modification) => Box::new(modification), Self::EditorApi(x) => Box::new(x), Self::ResourceHash(x) => Box::new(x), } @@ -230,7 +230,7 @@ macro_rules! tagged_value { Arc::new(list) } Self::DocumentNode(node) => Arc::new(node), - Self::ContextFeatures(features) => Arc::new(features), + Self::ContextModification(modification) => Arc::new(modification), Self::EditorApi(x) => Arc::new(x), Self::ResourceHash(x) => Arc::new(x), } @@ -258,7 +258,7 @@ macro_rules! tagged_value { Self::RenderOutput(_) => concrete!(RenderOutput), Self::NodeIdPath(_) => concrete!(List), Self::DocumentNode(_) => concrete!(DocumentNode), - Self::ContextFeatures(_) => concrete!(ContextFeatures), + Self::ContextModification(_) => concrete!(ContextModification), Self::EditorApi(_) => concrete!(Arc), Self::ResourceHash(_) => concrete!(ResourceHash), } @@ -308,7 +308,7 @@ macro_rules! tagged_value { Ok(value_edge(list)) } Self::DocumentNode(node) => Ok(value_edge(node)), - Self::ContextFeatures(features) => Ok(value_edge(features)), + Self::ContextModification(modification) => Ok(value_edge(modification)), Self::EditorApi(x) => Ok(value_edge(x)), Self::ResourceHash(x) => Ok(value_edge(x)), } @@ -441,7 +441,7 @@ macro_rules! tagged_value { Self::RenderOutput(_) => "RenderOutput".to_string(), Self::NodeIdPath(path) => format!("NodeIdPath({path:?})"), Self::DocumentNode(node) => format!("DocumentNode({node:?})"), - Self::ContextFeatures(features) => format!("ContextFeatures({features:?})"), + Self::ContextModification(modification) => format!("ContextModification({modification:?})"), Self::EditorApi(_) => "PlatformEditorApi".to_string(), Self::ResourceHash(hash) => format!("ResourceHash({hash:?})"), } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 84807f77a0..0de07b6102 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -308,7 +308,7 @@ impl ProtoNetwork { Ok(()) } - fn insert_context_nullification_node(&mut self, node_id: NodeId, context_deps: ContextFeatures) -> NodeId { + fn insert_context_nullification_node(&mut self, node_id: NodeId, context_deps: ContextModification) -> NodeId { let (_, node) = &self.nodes[node_id.0 as usize]; let mut path = node.original_location.path.clone(); @@ -338,7 +338,7 @@ impl ProtoNetwork { self.nodes.push(( nullification_value_node_id, ProtoNode { - construction_args: ConstructionArgs::Value(MemoHash::new(TaggedValue::ContextFeatures(context_deps))), + construction_args: ConstructionArgs::Value(MemoHash::new(TaggedValue::ContextModification(context_deps))), call_argument: concrete!(Context), identifier: ProtoNodeIdentifier::new("core_types::value::ClonedNode"), original_location: OriginalLocation { @@ -365,36 +365,40 @@ impl ProtoNetwork { nullification_node_id } - fn find_context_dependencies(&mut self, id: NodeId) -> (ContextFeatures, Option) { + fn find_context_dependencies(&mut self, id: NodeId) -> (ContextModification, Option) { let mut branch_dependencies = Vec::new(); - let mut combined_deps = ContextFeatures::default(); + let mut combined_deps = ContextModification::default(); let node_index = id.0 as usize; - let context_features = self.nodes[node_index].1.context_features; + let context_features = self.nodes[node_index].1.context_features.clone(); + let own_deps = ContextModification { + features: context_features.extract, + sources: context_features.sources.clone(), + }; 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 (context_features.extract, Some(id)), + ConstructionArgs::Value(_) => return (own_deps, Some(id)), ConstructionArgs::Nodes(items) => items.clone(), - ConstructionArgs::Inline(_) => return (context_features.extract, Some(id)), + ConstructionArgs::Inline(_) => return (own_deps, Some(id)), }; // Compute the dependencies for each branch and combine all of them for &node in &inputs { let branch = self.find_context_dependencies(node); + combined_deps |= &branch.0; branch_dependencies.push(branch); - combined_deps |= branch.0; } - let mut new_deps = combined_deps; + let mut new_deps = combined_deps.clone(); // Remove requirements which this node provides new_deps &= !context_features.inject; // Add requirements we have - new_deps |= context_features.extract; + new_deps |= own_deps; // If we either introduce new dependencies, we can cache all children which don't yet need that dependency - let we_introduce_new_deps = !combined_deps.contains(new_deps); + let we_introduce_new_deps = !combined_deps.contains(&new_deps); // For diverging branches, we can add a cache node for all branches which don't reqire all dependencies for (child_node, (deps, new_id)) in inputs.iter_mut().zip(branch_dependencies.into_iter()) { @@ -410,15 +414,15 @@ impl ProtoNetwork { let net_injections = context_features.inject.difference(context_features.extract); // Which dependencies still need to be met after this node? - let remaining_deps_from_children = combined_deps.difference(net_injections); + let remaining_deps_from_children = combined_deps.features.difference(net_injections); // Do we satisfy any existing dependencies? - let we_supply_existing_deps = !combined_deps.difference(remaining_deps_from_children).is_empty(); + let we_supply_existing_deps = !combined_deps.features.difference(remaining_deps_from_children).is_empty(); let mut new_id = None; if we_supply_existing_deps { // Our set of context dependencies has shrunk so we can add a cache node after the current node - new_id = Some(self.insert_context_nullification_node(id, new_deps)); + new_id = Some(self.insert_context_nullification_node(id, new_deps.clone())); } (new_deps, new_id) diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 5449e326c8..9356290fa5 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -188,18 +188,18 @@ fn node_registry() -> HashMap> { async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]), // Context nullification #[cfg(feature = "gpu")] - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeDyn, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => ListDyn, Context => graphene_std::ContextFeatures]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeDyn, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => ListDyn, Context => graphene_std::ContextModification]), #[cfg(target_family = "wasm")] - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuExecutorHandle, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuPipelineCache, Context => graphene_std::ContextFeatures]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuExecutorHandle, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option, Context => graphene_std::ContextModification]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuPipelineCache, Context => graphene_std::ContextModification]), // ========== // MEMO NODES // ========== @@ -247,7 +247,7 @@ fn node_registry() -> HashMap> { async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DocumentNode]), - async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]), + async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextModification]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Box]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]), diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index c151f85d68..58758e4d37 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -33,6 +33,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash, dyn_any::DynAny, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ContextModification { + pub features: ContextFeatures, + /// Must stay sorted. + pub sources: Vec, +} + +impl core::ops::BitOrAssign<&ContextModification> for ContextModification { + fn bitor_assign(&mut self, other: &Self) { + self.features |= other.features; + merge_sorted_sources(&mut self.sources, &other.sources); + } +} + +impl core::ops::BitOrAssign for ContextModification { + fn bitor_assign(&mut self, other: Self) { + *self |= &other; + } +} + +impl core::ops::BitOrAssign for ContextModification { + fn bitor_assign(&mut self, features: ContextFeatures) { + self.features |= features; + } +} + +impl core::ops::BitAndAssign for ContextModification { + fn bitand_assign(&mut self, features: ContextFeatures) { + self.features &= features; + } +} + +impl ContextModification { + pub fn contains(&self, other: &Self) -> bool { + self.features.contains(other.features) && other.sources.iter().all(|id| self.sources.binary_search(id).is_ok()) + } + + pub fn difference(&self, other: &Self) -> Self { + Self { + features: self.features.difference(other.features), + sources: self.sources.iter().copied().filter(|id| other.sources.binary_search(id).is_err()).collect(), + } + } + + pub fn is_empty(&self) -> bool { + self.features.is_empty() && self.sources.is_empty() + } +} + +pub fn merge_sorted_sources(sources: &mut Vec, other: &[SourceId]) { + for &id in other { + if let Err(insert_at) = sources.binary_search(&id) { + sources.insert(insert_at, id); + } + } } impl From<&[ContextFeature]> for ContextDependencies { @@ -227,7 +288,11 @@ impl From<&[ContextFeature]> for ContextDependencies { _ => ContextFeatures::empty(), }; } - Self { extract, inject } + Self { + extract, + inject, + sources: Vec::new(), + } } } @@ -747,53 +812,52 @@ impl<'a> EvalScope<'a> { arena, hash: 0, }; - scope.hash = scope.compute_hash(None); + scope.hash = scope.compute_hash(|_| true); scope } - pub fn retained(&self, retain: &[SourceId]) -> EvalScope<'a> { - EvalScope { - hash: self.compute_hash(Some(retain)), - ..*self - } - } - pub fn with_real_time(&self, real_time: Option) -> EvalScope<'a> { let mut scope = EvalScope { real_time, ..*self }; - scope.hash = scope.compute_hash(None); + scope.hash = scope.compute_hash(|_| true); scope } pub fn with_animation_time(&self, animation_time: Option) -> EvalScope<'a> { let mut scope = EvalScope { animation_time, ..*self }; - scope.hash = scope.compute_hash(None); + scope.hash = scope.compute_hash(|_| true); scope } pub fn with_pointer_position(&self, pointer_position: Option) -> EvalScope<'a> { let mut scope = EvalScope { pointer_position, ..*self }; - scope.hash = scope.compute_hash(None); + scope.hash = scope.compute_hash(|_| true); scope } - pub fn nullified(&self, keep: ContextFeatures) -> EvalScope<'a> { + pub fn nullified(&self, keep: ContextFeatures, retain: Option<&[SourceId]>) -> EvalScope<'a> { let mut scope = EvalScope { real_time: self.real_time.filter(|_| keep.contains(ContextFeatures::REAL_TIME)), animation_time: self.animation_time.filter(|_| keep.contains(ContextFeatures::ANIMATION_TIME)), pointer_position: self.pointer_position.filter(|_| keep.contains(ContextFeatures::POINTER_POSITION)), ..*self }; - scope.hash = scope.compute_hash(None); + scope.hash = scope.compute_hash(|source| retain.is_none_or(|retain| retain.contains(source))); scope } - fn compute_hash(&self, retain: Option<&[SourceId]>) -> u64 { + pub fn excluding(&self, source: SourceId) -> EvalScope<'a> { + let mut scope = *self; + scope.hash = scope.compute_hash(|candidate| *candidate != source); + scope + } + + fn compute_hash(&self, keep_source: impl Fn(&SourceId) -> bool) -> u64 { let mut hasher = std::hash::DefaultHasher::new(); self.real_time.map(f64::to_bits).hash(&mut hasher); self.animation_time.map(f64::to_bits).hash(&mut hasher); self.pointer_position.map(|position| (position.x.to_bits(), position.y.to_bits())).hash(&mut hasher); for (source, generation) in self.generations { - if retain.is_none_or(|retain| retain.contains(source)) { + if keep_source(source) { (source, generation).hash(&mut hasher); } } @@ -1360,7 +1424,7 @@ mod context_impl_tests { let bumped_retained = [(0, 1), (1, 4)]; let hash_with = |generations: &[(SourceId, u64)]| { - let scope = scope_fixture(generations, &arena).retained(&[1]); + let scope = scope_fixture(generations, &arena).nullified(ContextFeatures::all(), Some(&[1])); let retained_scope_context = ContextImpl::root(&scope); hash_of(&retained_scope_context) }; diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index 02c9498346..58ec8f2555 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -58,6 +58,7 @@ pub enum RegistryValueSource { None, Default(&'static str), Scope(&'static str), + SourceId, } type NodeRegistry = LazyLock>>>; diff --git a/node-graph/libraries/core-types/src/runtime.rs b/node-graph/libraries/core-types/src/runtime.rs index c6001285bf..891b02499a 100644 --- a/node-graph/libraries/core-types/src/runtime.rs +++ b/node-graph/libraries/core-types/src/runtime.rs @@ -409,8 +409,7 @@ mod tests { let snapshot = runtime.snapshot(); let scope = EvalScope::new(None, None, None, &snapshot, &arena); - let source_scope = scope.retained(&[]); - let ctx = ContextImpl::root(&source_scope); + let ctx = ContextImpl::root(&scope); assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); assert!(!runtime.take_dirty()); @@ -420,9 +419,8 @@ mod tests { assert_eq!(bumped, vec![(11, 1)]); let bumped_scope = EvalScope::new(None, None, None, &bumped, &arena); - let bumped_source_scope = bumped_scope.retained(&[]); - let bumped_ctx = ContextImpl::root(&bumped_source_scope); - assert_eq!(GNode::eval(&graph, &bumped_ctx), GPoll::Final(42.0), "the retained key replays the landed slot"); + let bumped_ctx = ContextImpl::root(&bumped_scope); + assert_eq!(GNode::eval(&graph, &bumped_ctx), GPoll::Final(42.0), "the own-generation-excluded key replays the landed slot"); assert_eq!(runtime.spawner().drain(), 0, "a slot hit must not respawn"); let downstream_key = crate::registry::cache_key(&ContextImpl::root(&scope)); diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index ccdff43798..4507b3eaa8 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -149,6 +149,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn quote!(RegistryValueSource::Scope(#data.as_static_str())) } } + ParsedValueSource::SourceId => quote!(RegistryValueSource::SourceId), _ => quote!(RegistryValueSource::None), }, _ => quote!(RegistryValueSource::None), diff --git a/node-graph/node-macro/src/gcodegen.rs b/node-graph/node-macro/src/gcodegen.rs index a3d4a60012..1ddb4bb067 100644 --- a/node-graph/node-macro/src/gcodegen.rs +++ b/node-graph/node-macro/src/gcodegen.rs @@ -40,8 +40,8 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF .collect(), None => vec![quote!(#core_types::Ctx)], }; - if async_source { - ctx_bounds.push(quote!(#core_types::CacheHash)); + if async_source && !snapshot_ctx { + ctx_bounds.push(quote!(#core_types::context::DeriveCtx)); } if snapshot_ctx { ctx_bounds.extend([ @@ -144,7 +144,7 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF } }); - let async_bounds = match (async_fn, future_kernel) { + let mut async_bounds = match (async_fn, future_kernel) { (false, false) => Vec::new(), (false, true) => vec![quote!(#trait_output: Clone)], (true, _) => { @@ -160,6 +160,9 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF output_clone.chain(value_clones).chain(data_clones).collect() } }; + if async_source { + async_bounds.push(quote!(for<'__derived> #core_types::context::Derived<'__derived, #ctx_ident>: #core_types::CacheHash)); + } let clampable_bounds = regular_fields.iter().filter_map(|field| { let ParsedFieldType::Regular(RegularParsedField { ty, number_hard_min, number_hard_max, .. }) = &field.ty else { @@ -326,7 +329,8 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF None => quote!(#core_types::gpoll::GPoll::Pending), }; let slot_check = quote! { - let __key = #core_types::registry::cache_key(__input); + let __scope = #core_types::context::DeriveCtx::scope(__input).excluding(_source); + let __key = #core_types::registry::cache_key(&#core_types::context::DeriveCtx::with_scope(__input, &__scope)); { let __entries = self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner); if let Some(__state) = __entries.get(&__key) { diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 405d08f784..58ad83c8b7 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -72,6 +72,7 @@ pub enum ParsedValueSource { None, Default(TokenStream2), Scope(Expr), + SourceId, } // #[widget(ParsedWidgetOverride::Hidden)] @@ -1063,7 +1064,7 @@ impl ParsedNodeFn { parse_quote!(#core_types::runtime::RuntimeHandle), ParsedValueSource::Scope(parse_quote!("graphene_std::runtime::RuntimeNode")), )); - self.fields.push(hidden_field("_source", parse_quote!(#core_types::SourceId), ParsedValueSource::None)); + self.fields.push(hidden_field("_source", parse_quote!(#core_types::SourceId), ParsedValueSource::SourceId)); } } diff --git a/node-graph/nodes/gcore/src/context_modification.rs b/node-graph/nodes/gcore/src/context_modification.rs index fad88894e9..104d82507c 100644 --- a/node-graph/nodes/gcore/src/context_modification.rs +++ b/node-graph/nodes/gcore/src/context_modification.rs @@ -1,5 +1,5 @@ use core::f64; -use core_types::context::{Context, ContextFeatures, Ctx, DeriveCtx}; +use core_types::context::{Context, ContextModification, Ctx, DeriveCtx}; use core_types::gpoll::GPoll; use core_types::list::{AttributeDyn, AttributeValueDyn, List, ListDyn}; use core_types::transform::Footprint; @@ -44,8 +44,8 @@ fn context_modification( )] value: impl Node, Output = T>, /// The parts of the context to keep when evaluating the input value. All other parts are nullified. - features_to_keep: ContextFeatures, + modification: ContextModification, ) -> GPoll { - let scope = ctx.scope().nullified(features_to_keep); - value.eval(&ctx.nullified(features_to_keep, &scope)) + let scope = ctx.scope().nullified(modification.features, Some(&modification.sources)); + value.eval(&ctx.nullified(modification.features, &scope)) } diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 5217e51da4..992b16399c 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -279,6 +279,7 @@ pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTyp } } RegistryValueSource::Scope(data) => return NodeInput::scope(*data), + RegistryValueSource::SourceId => return NodeInput::Reflection(DocumentNodeMetadata::SourceId), }; if let Some(type_default) = TaggedValue::from_type(ty) {