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 86c93daf72..9e512d76e4 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -96,33 +96,6 @@ impl NodeNetworkInterface { } } - /// Append the hidden runtime and source-id inputs to async-source protonodes saved before their injection. - /// Runs after the identifier replacement pass, so it matches only current identifier spellings. - pub fn migrate_async_source_inputs(&mut self) { - const PRE_INJECTION_ARITIES: [(&str, usize); 5] = [ - ("graphene_std::platform_application_io::GetRequestNode", 4), - ("graphene_std::platform_application_io::PostRequestNode", 5), - ("graphene_std::platform_application_io::LoadResourceNode", 2), - ("graphene_std::platform_application_io::RasterizeNode", 3), - ("graphene_std::platform_application_io::ResourceNode", 2), - ]; - fix_network(self.document_network_mut()); - fn fix_network(network: &mut NodeNetwork) { - for node in network.nodes.values_mut() { - if let Some(network) = node.implementation.get_network_mut() { - fix_network(network); - } - if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation - && let Some(base) = protonode.as_str().split('<').next() - && let Some((_, arity)) = PRE_INJECTION_ARITIES.iter().find(|(identifier, _)| *identifier == base) - && node.inputs.len() == *arity - { - node.inputs.push(NodeInput::scope("graphene_std::runtime::RuntimeNode")); - node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::SourceId)); - } - } - } - } } // Public immutable getters for the network interface diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 871b9fcbe3..4bad328df5 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -1148,8 +1148,6 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ } } - document.network_interface.migrate_async_source_inputs(); - // The "Brush" wrapper network was replaced with the `brush` proto node directly. Convert old `Network("Brush")` instances to the proto node, forwarding all 3 inputs (Background, Trace, Cache) one-to-one. // This must run as a pre-pass before the recursive iteration below: replacing the outer Brush's network impl orphans its child paths, and the recursive iteration would log errors for those stale paths. let brush_layers: Vec<(NodeId, Vec)> = document diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index 25d196d228..c0754313d1 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -53,11 +53,16 @@ impl EditorTestUtils { } runtime.run().await; - // An async source reports `Pending` on the evaluation that starts it and marks the runtime dirty - // once it completes, so the value only reaches the render on a follow-up evaluation. That first - // response is superseded, so it is drained rather than asserted on. + // An async source reports `Pending` on the evaluation that starts it and marks the runtime dirty once + // it completes, so the value only reaches the render on a follow-up evaluation. The superseded response's + // `Pending` error is ignored, but its messages carry the incremental resolved-types delta and must be + // dispatched, or the editor's type map desyncs permanently. while runtime.take_dirty() { - let _ = editor.poll_node_graph_evaluation(&mut VecDeque::new()); + let mut superseded_messages = VecDeque::new(); + let _ = editor.poll_node_graph_evaluation(&mut superseded_messages); + for message in superseded_messages { + editor.handle_message(message); + } let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler; let (executor, documents) = (&mut portfolio.executor, &mut portfolio.documents); diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index 14464dd89c..18e32700e7 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -20,6 +20,8 @@ pub struct NodeMetadata { pub context_features: Vec, pub memoize: bool, pub inject_scope: bool, + /// The macro appended its hidden `_runtime` and `_source` fields as the last two entries of `fields`. + pub async_source_fields: bool, } // Translation struct between macro and definition diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 22a50d4353..7951a13786 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -18,7 +18,6 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn fn_generics, input, output_type, - is_async, fields, description, .. @@ -113,7 +112,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn quote! { pub(super) #name: #r#gen } }); - let async_source = *is_async || is_source_kernel(output_type); + let async_source = parsed.injects_async_source_fields(); let slot_value_type = slot_value_type(output_type); let slot_field = async_source .then(|| quote! { pub(super) slot: std::sync::Arc>>>> }) @@ -356,6 +355,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn context_features: vec![#(ContextFeature::#context_features,)*], memoize: #memoize_flag, inject_scope: #inject_scope_flag, + async_source_fields: #async_source, fields: vec![ #( FieldMetadata { diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index e752dd91cc..605b2ba91c 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -998,7 +998,7 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result bool { + self.is_async || crate::codegen::is_source_kernel(&self.output_type) + } + pub fn inject_async_source_fields(&mut self, core_types: &TokenStream2) { let hidden_field = |name: &str, ty: Type, value_source: ParsedValueSource| ParsedField { pat_ident: PatIdent { diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 0c1573d172..200839afe8 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -116,7 +116,13 @@ impl Preprocessor { for (id, metadata) in core_types::registry::NODE_METADATA.lock().unwrap().iter() { let id = id.clone(); - let NodeMetadata { fields, memoize, inject_scope, .. } = metadata; + let NodeMetadata { + fields, + memoize, + inject_scope, + async_source_fields, + .. + } = metadata; let Some(implementations) = node_registry.get(&id) else { continue }; let valid_call_args: HashSet<_> = implementations.iter().map(|entry| entry.io.call_argument.clone()).collect(); let first_node_io = implementations.first().map(|entry| &entry.io).unwrap_or(const { &NodeIOTypes::empty() }); @@ -132,30 +138,12 @@ impl Preprocessor { } let mut inputs: Vec<_> = node_inputs(fields, first_node_io); - let input_count = inputs.len(); + let wrapper_input_count = inputs.len() - if *async_source_fields { 2 } else { 0 }; - // The node macro appends a `RuntimeHandle` scope field and a `SourceId` reflection field to every - // async or source kernel. They are resolved inside the substitution network, so the wrapper neither - // exposes them nor carries their context modification. - let injected_field_count = match &fields[..] { - [.., runtime, source] if matches!(runtime.value_source, RegistryValueSource::Scope(_)) && matches!(source.value_source, RegistryValueSource::SourceId) => 2, - _ => 0, - }; - let wrapper_input_count = input_count - injected_field_count; - - inputs.truncate(wrapper_input_count); - - // The injected fields go straight onto the inner node, so the source it reflects is recorded on the - // node that consumes it rather than on a forwarding node that later gets dissolved. - let network_inputs = (0..input_count) - .map(|i| { - if i < wrapper_input_count { - NodeInput::node(NodeId(i as u64), 0) - } else { - injected_field_input(&fields[i]) - } - }) - .collect(); + // The injected fields must not surface as wrapper inputs, and the `_source` reflection must sit on + // the kernel itself so the source id lands on a node that survives flattening. + let injected_inputs = inputs.split_off(wrapper_input_count); + let network_inputs = (0..wrapper_input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).chain(injected_inputs).collect(); let passthrough_node = ops::passthrough::IDENTIFIER; @@ -207,7 +195,7 @@ impl Preprocessor { }) .collect(); - if generated_nodes == 0 && !memoize && !inject_scope && injected_field_count == 0 { + if generated_nodes == 0 && !memoize && !inject_scope && !async_source_fields { continue; } @@ -279,14 +267,6 @@ impl Preprocessor { } } -fn injected_field_input(field: ®istry::FieldMetadata) -> NodeInput { - match field.value_source { - RegistryValueSource::Scope(data) => NodeInput::scope(data), - RegistryValueSource::SourceId => NodeInput::Reflection(DocumentNodeMetadata::SourceId), - _ => NodeInput::value(TaggedValue::None, false), - } -} - pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTypes) -> Vec { fields .iter()