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 e18887880e..78098dc950 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 @@ -43,7 +43,7 @@ pub(super) fn post_process_nodes(custom: Vec) -> HashMap description, properties, context_features, - memoize: _, + .. } = metadata; let implementations = node_registry.get(id).unwrap_or(&empty_implementations); diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index df671f1809..ea6cbb3ff1 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -3,11 +3,11 @@ use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use glam::{DAffine2, DVec2, UVec2}; use graph_craft::application_io::resource::ResourceRegistry; use graph_craft::application_io::{PlatformApplicationIo, PlatformEditorApi}; +use graph_craft::concrete; use graph_craft::document::value::{RenderOutput, RenderOutputType, TaggedValue}; use graph_craft::document::{NodeId, NodeNetwork}; use graph_craft::graphene_compiler::Compiler; use graph_craft::proto::GraphErrors; -use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::application_io::{ApplicationIo, ExportFormat, ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_std::bounds::{BoundingBox, RenderBoundingBox}; use graphene_std::list::List; @@ -49,8 +49,7 @@ pub struct NodeRuntime { /// Which node is inspected and which monitor node is used (if any) for the current execution. inspect_state: Option, - /// Mapping of the fully-qualified node paths to their preprocessor substitutions. - substitutions: HashMap, + preprocessor: preprocessor::Preprocessor, // TODO: Remove, it doesn't need to be persisted anymore /// The current renders of the thumbnails for layer nodes. @@ -146,7 +145,7 @@ impl NodeRuntime { node_graph_errors: Vec::new(), monitor_nodes: Vec::new(), - substitutions: preprocessor::generate_node_substitutions(), + preprocessor: preprocessor::Preprocessor::new(), thumbnail_renders: Default::default(), vector_modify: Default::default(), @@ -347,7 +346,7 @@ impl NodeRuntime { } async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { - if let Err(e) = preprocessor::expand_network(&mut graph, &self.substitutions, &self.resources) { + if let Err(e) = self.preprocessor.expand_network(&mut graph, &self.resources) { return Err((ResolvedDocumentNodeTypesDelta::default(), e.to_string())); } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 40c0304ae5..21b61f7377 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -731,9 +731,9 @@ impl NodeNetwork { } /// Replace all references in any node of `old_output` with `new_output` - fn replace_network_outputs(&mut self, old_output: NodeInput, new_output: NodeInput) { + fn replace_network_outputs(&mut self, old_output: &NodeInput, new_output: &NodeInput) { for output in self.exports.iter_mut() { - if *output == old_output { + if *output == *old_output { *output = new_output.clone(); } } @@ -899,27 +899,44 @@ impl NodeNetwork { } // TODO: Add support for flattening exports that are NodeInput::Import (https://github.com/GraphiteEditor/Graphite/issues/1762) + self.replace_node_with_its_exports(id, &node.original_location, &inner_network.exports); + + for node_id in new_nodes { + self.flatten_with_fns(node_id, map_ids, gen_id); + } + } + + fn replace_node_with_its_exports(&mut self, id: NodeId, original_location: &OriginalLocation, exports: &[NodeInput]) { + // Connect scope injections to the inner network export + self.scope_injections.values_mut().for_each(|(node_id, _ty)| { + if node_id == &id { + let Some(export) = exports.first() else { + log::error!("Inner network should have at least one export"); + return; + }; + if let NodeInput::Node { node_id: export_id, output_index: _ } = export { + *node_id = *export_id; + } + } + }); + // Connect all nodes that were previously connected to this node to the nodes of the inner network - for (i, export) in inner_network.exports.into_iter().enumerate() { + for (i, export) in exports.iter().enumerate() { if let NodeInput::Node { node_id, output_index, .. } = &export { - for deps in &node.original_location.dependants { + for deps in &original_location.dependants { for dep in deps { self.replace_node_inputs(*dep, (id, i), (*node_id, *output_index)); } } if let Some(new_output_node) = self.nodes.get_mut(node_id) { - for dep in &node.original_location.dependants[i] { + for dep in &original_location.dependants[i] { new_output_node.original_location.dependants[*output_index].push(*dep); } } } - self.replace_network_outputs(NodeInput::node(id, i), export); - } - - for node_id in new_nodes { - self.flatten_with_fns(node_id, map_ids, gen_id); + self.replace_network_outputs(&NodeInput::node(id, i), export); } } diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index a5e0151fec..681f972f2d 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -242,8 +242,8 @@ fn compile_graph(document_string: String, editor_api: Arc) -> let mut network = load_network(&document_string); fix_nodes(&mut network); - let substitutions = preprocessor::generate_node_substitutions(); - preprocessor::expand_network(&mut network, &substitutions, &ResourceRegistry::default()).expect("Failed to expand network"); // TODO: actually load the resources from the document + let preprocessor = preprocessor::Preprocessor::new(); + preprocessor.expand_network(&mut network, &ResourceRegistry::default()).expect("Failed to expand network"); // TODO: actually load the resources from the document let wrapped_network = wrap_network_in_scope(network.clone(), editor_api); diff --git a/node-graph/interpreted-executor/benches/benchmark_util.rs b/node-graph/interpreted-executor/benches/benchmark_util.rs index b92e21e869..47361f2ef3 100644 --- a/node-graph/interpreted-executor/benches/benchmark_util.rs +++ b/node-graph/interpreted-executor/benches/benchmark_util.rs @@ -11,8 +11,8 @@ use interpreted_executor::util::wrap_network_in_scope; pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) { let mut network = load_from_name(name); let editor_api = std::sync::Arc::new(EditorApi::default()); - let substitutions = preprocessor::generate_node_substitutions(); - preprocessor::expand_network(&mut network, &substitutions, &ResourceRegistry::default()).unwrap(); + let preprocessor = preprocessor::Preprocessor::new(); + preprocessor.expand_network(&mut network, &ResourceRegistry::default()).unwrap(); let network = wrap_network_in_scope(network, editor_api); let proto_network = compile(network); let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap(); diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index 9e9f12673d..d9ead39db8 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -17,6 +17,7 @@ pub struct NodeMetadata { pub properties: Option<&'static str>, pub context_features: Vec, pub memoize: bool, + pub inject_scope: bool, } // Translation struct between macro and definition diff --git a/node-graph/libraries/core-types/src/types.rs b/node-graph/libraries/core-types/src/types.rs index cd4f6700e3..5e70dcbced 100644 --- a/node-graph/libraries/core-types/src/types.rs +++ b/node-graph/libraries/core-types/src/types.rs @@ -154,6 +154,13 @@ impl ProtoNodeIdentifier { pub fn as_str(&self) -> &str { self.name.as_ref() } + + pub const fn as_static_str(&self) -> &'static str { + match self.name { + Cow::Borrowed(name) => name, + Cow::Owned(_) => panic!("`as_static_str` called on a `ProtoNodeIdentifier` backed by an owned string"), + } + } } impl Display for ProtoNodeIdentifier { diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 72629fb7bd..b3b04da441 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -172,7 +172,13 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn quote!(RegistryValueSource::Default(stringify!(#data))) } } - ParsedValueSource::Scope(data) => quote!(RegistryValueSource::Scope(#data)), + ParsedValueSource::Scope(data) => { + if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(_), .. }) = data { + quote!(RegistryValueSource::Scope(#data)) + } else { + quote!(RegistryValueSource::Scope(#data.as_static_str())) + } + } _ => quote!(RegistryValueSource::None), }, _ => quote!(RegistryValueSource::None), @@ -402,6 +408,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None)); let memoize_flag = attributes.memoize; + let inject_scope_flag = attributes.inject_scope; let cfg = crate::shader_nodes::modify_cfg(attributes); let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, core_types, &identifier, &cfg); @@ -500,6 +507,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn properties: #properties, context_features: vec![#(ContextFeature::#context_features,)*], memoize: #memoize_flag, + inject_scope: #inject_scope_flag, fields: vec![ #( FieldMetadata { diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 8d2420b940..f0641685aa 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -54,6 +54,8 @@ pub(crate) struct NodeFnAttributes { pub(crate) serialize: Option, /// Whether the preprocessor should add a Memoize node after this node in the generated subnetwork pub(crate) memoize: bool, + /// Whether this node provides a scope + pub(crate) inject_scope: bool, } #[derive(Clone, Debug, Default)] @@ -61,7 +63,7 @@ pub enum ParsedValueSource { #[default] None, Default(TokenStream2), - Scope(LitStr), + Scope(Expr), } // #[widget(ParsedWidgetOverride::Hidden)] @@ -261,6 +263,7 @@ impl Parse for NodeFnAttributes { let mut shader_node = None; let mut serialize = None; let mut memoize = false; + let mut inject_scope = false; let content = input; // let content; @@ -391,13 +394,25 @@ impl Parse for NodeFnAttributes { } memoize = true; } + // Instructs the preprocessor to make this node available as a scope. + // Other nodes can then access it with `#[scope(node::IDENTIFIER)]`. + // + // Example usage: + // #[node_macro::node(..., inject_scope, ...)] + "inject_scope" => { + let path = meta.require_path_only()?; + if inject_scope { + return Err(Error::new_spanned(path, "Multiple 'inject_scope' attributes are not allowed")); + } + inject_scope = true; + } _ => { return Err(Error::new_spanned( meta, indoc!( r#" Unsupported attribute in `node`. - Supported attributes are 'category', 'name', 'path', 'skip_impl', 'properties', 'cfg', 'shader_node', 'serialize', and 'memoize'. + Supported attributes are 'category', 'name', 'path', 'skip_impl', 'properties', 'cfg', 'shader_node', 'serialize', 'memoize', and 'inject_scope'. Example usage: #[node_macro::node(..., name("Test Node"), ...)] "# @@ -430,6 +445,7 @@ impl Parse for NodeFnAttributes { shader_node, serialize, memoize, + inject_scope, }) } } @@ -979,7 +995,7 @@ mod tests { assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string()); } (ParsedValueSource::Scope(p), ParsedValueSource::Scope(e)) => { - assert_eq!(p.value(), e.value()); + assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string()); } _ => panic!("Mismatched default values"), } @@ -1038,6 +1054,7 @@ mod tests { shader_node: None, serialize: None, memoize: false, + inject_scope: false, }, fn_name: Ident::new("add", Span::call_site()), struct_name: Ident::new("Add", Span::call_site()), @@ -1107,6 +1124,7 @@ mod tests { shader_node: None, serialize: None, memoize: false, + inject_scope: false, }, fn_name: Ident::new("transform", Span::call_site()), struct_name: Ident::new("Transform", Span::call_site()), @@ -1190,6 +1208,7 @@ mod tests { shader_node: None, serialize: None, memoize: false, + inject_scope: false, }, fn_name: Ident::new("circle", Span::call_site()), struct_name: Ident::new("Circle", Span::call_site()), @@ -1255,6 +1274,7 @@ mod tests { shader_node: None, serialize: None, memoize: false, + inject_scope: false, }, fn_name: Ident::new("levels", Span::call_site()), struct_name: Ident::new("Levels", Span::call_site()), @@ -1332,6 +1352,7 @@ mod tests { shader_node: None, serialize: None, memoize: false, + inject_scope: false, }, fn_name: Ident::new("add", Span::call_site()), struct_name: Ident::new("Add", Span::call_site()), @@ -1397,6 +1418,7 @@ mod tests { shader_node: None, serialize: None, memoize: false, + inject_scope: false, }, fn_name: Ident::new("load_image", Span::call_site()), struct_name: Ident::new("LoadImage", Span::call_site()), @@ -1462,6 +1484,7 @@ mod tests { shader_node: None, serialize: None, memoize: false, + inject_scope: false, }, fn_name: Ident::new("custom_node", Span::call_site()), struct_name: Ident::new("CustomNode", Span::call_site()), diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index f551e11821..06a1c73eff 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -233,7 +233,7 @@ impl PerPixelAdjustCodegen<'_> { ty: ParsedFieldType::Regular(RegularParsedField { ty: parse_quote!(&'a WgpuExecutor), exposed: true, - value_source: ParsedValueSource::Scope(LitStr::new("wgpu-executor", Span::call_site())), + value_source: ParsedValueSource::Scope(parse_quote!("wgpu-executor")), number_soft_min: None, number_soft_max: None, number_hard_min: None, diff --git a/node-graph/nodes/gcore/src/debug.rs b/node-graph/nodes/gcore/src/debug.rs index 30b042ddee..24bf789082 100644 --- a/node-graph/nodes/gcore/src/debug.rs +++ b/node-graph/nodes/gcore/src/debug.rs @@ -34,3 +34,13 @@ fn unwrap_option(_: impl Ctx, #[implementations(Option, Option< fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&List>)] value: &'i T) -> T { value.clone() } + +#[node_macro::node(category("Debug"), inject_scope)] +fn inject_scope_test_producer(_: impl Ctx) -> bool { + true +} + +#[node_macro::node(category("Debug"))] +fn inject_scope_test_consumer(_: impl Ctx, _primary: (), #[scope(inject_scope_test_producer::IDENTIFIER)] injected: bool) -> bool { + injected +} diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index aa09982bfd..d2e05724dd 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -1,6 +1,7 @@ #[macro_use] extern crate log; +use graph_craft::Type; use graph_craft::application_io::resource::{ResourceId, ResourceRegistry}; use graph_craft::document::value::*; use graph_craft::document::*; @@ -9,11 +10,21 @@ use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::registry::*; use graphene_std::*; use std::collections::{HashMap, HashSet}; +use std::hash::{DefaultHasher, Hash, Hasher}; -pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap, resources: &ResourceRegistry) -> Result<(), PreprocessorError> { - replace_resource_inputs(network, resources)?; - expand_network_inner(network, substitutions); - Ok(()) +#[derive(Debug, Default, Clone)] +pub struct Preprocessor { + substitutions: HashMap, + inject_scopes: HashMap, +} + +impl Preprocessor { + pub fn expand_network(&self, network: &mut NodeNetwork, resources: &ResourceRegistry) -> Result<(), PreprocessorError> { + self.insert_inject_scopes(network); + replace_resource_inputs(network, resources)?; + self.expand_network_inner(network); + Ok(()) + } } /// Replace every `TaggedValue::Resource(hash)` input with a reference to a freshly inserted `resource` proto node. @@ -61,160 +72,191 @@ fn replace_resource_inputs(network: &mut NodeNetwork, resources: &ResourceRegist Ok(()) } -fn expand_network_inner(network: &mut NodeNetwork, substitutions: &HashMap) { - if network.generated { - return; +impl Preprocessor { + fn insert_inject_scopes(&self, network: &mut NodeNetwork) { + for (identifier, (template, ty)) in self.inject_scopes.iter() { + let mut hasher = DefaultHasher::new(); + + identifier.as_str().hash(&mut hasher); + let producer_id = NodeId(hasher.finish()); + network.nodes.insert(producer_id, template.clone()); + + network.scope_injections.insert(identifier.as_str().to_string(), (producer_id, ty.clone())); + } } - for node in network.nodes.values_mut() { - match &mut node.implementation { - DocumentNodeImplementation::Network(node_network) => expand_network_inner(node_network, substitutions), - DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { - if let Some(new_node) = substitutions.get(proto_node_identifier) { - // Reconcile the document node's inputs with what the current node definition expects, - // since the saved document may have fewer or more inputs than the current version - while node.inputs.len() < new_node.inputs.len() { - node.inputs.push(new_node.inputs[node.inputs.len()].clone()); - } - node.inputs.truncate(new_node.inputs.len()); + fn expand_network_inner(&self, network: &mut NodeNetwork) { + if network.generated { + return; + } - node.implementation = new_node.implementation.clone(); + for node in network.nodes.values_mut() { + match &mut node.implementation { + DocumentNodeImplementation::Network(node_network) => self.expand_network_inner(node_network), + DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { + if let Some(new_node) = self.substitutions.get(proto_node_identifier) { + // Reconcile the document node's inputs with what the current node definition expects, + // since the saved document may have fewer or more inputs than the current version + while node.inputs.len() < new_node.inputs.len() { + node.inputs.push(new_node.inputs[node.inputs.len()].clone()); + } + node.inputs.truncate(new_node.inputs.len()); + + node.implementation = new_node.implementation.clone(); + } } + DocumentNodeImplementation::Extract => (), } - DocumentNodeImplementation::Extract => (), } } -} -pub fn generate_node_substitutions() -> HashMap { - let mut custom = HashMap::new(); - // We pre initialize the node registry here to avoid a deadlock - let into_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY; - let node_registry = core_types::registry::NODE_REGISTRY.lock().unwrap(); - for (id, metadata) in core_types::registry::NODE_METADATA.lock().unwrap().iter() { - let id = id.clone(); - - let NodeMetadata { fields, memoize, .. } = metadata; - let Some(implementations) = node_registry.get(&id) else { continue }; - let valid_call_args: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); - let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); - let mut node_io_types = vec![HashSet::new(); fields.len()]; - for (_, node_io) in implementations.iter() { - for (i, ty) in node_io.inputs.iter().enumerate() { - node_io_types[i].insert(ty.clone()); + pub fn new() -> Self { + let mut substitutions = HashMap::new(); + let mut inject_scopes = HashMap::new(); + // We pre initialize the node registry here to avoid a deadlock + let into_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY; + let node_registry = core_types::registry::NODE_REGISTRY.lock().unwrap(); + for (id, metadata) in core_types::registry::NODE_METADATA.lock().unwrap().iter() { + let id = id.clone(); + + let NodeMetadata { fields, memoize, inject_scope, .. } = metadata; + let Some(implementations) = node_registry.get(&id) else { continue }; + let valid_call_args: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); + let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); + let mut node_io_types = vec![HashSet::new(); fields.len()]; + for (_, node_io) in implementations.iter() { + for (i, ty) in node_io.inputs.iter().enumerate() { + node_io_types[i].insert(ty.clone()); + } + } + let mut input_type = &first_node_io.call_argument; + if valid_call_args.len() > 1 { + input_type = &const { generic!(D) }; } - } - let mut input_type = &first_node_io.call_argument; - if valid_call_args.len() > 1 { - input_type = &const { generic!(D) }; - } - let inputs: Vec<_> = node_inputs(fields, first_node_io); - let input_count = inputs.len(); - let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); - - let passthrough_node = ops::passthrough::IDENTIFIER; - - let mut generated_nodes = 0; - let mut nodes: HashMap<_, _, _> = node_io_types - .iter() - .take(input_count) - .enumerate() - .map(|(i, inputs)| { - ( - NodeId(i as u64), - match inputs.len() { - 1 => { - let input = inputs.iter().next().unwrap(); - let input_ty = input.nested_type(); - let mut inputs = vec![NodeInput::import(input.clone(), i)]; - - let into_node_identifier = ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::IntoNode<{}>", input_ty.identifier_name())); - let convert_node_identifier = ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::ConvertNode<{}>", input_ty.identifier_name())); - - let proto_node = if into_node_registry.keys().any(|ident: &ProtoNodeIdentifier| ident.as_str() == into_node_identifier.as_str()) { - generated_nodes += 1; - into_node_identifier - } else if into_node_registry.keys().any(|ident| ident.as_str() == convert_node_identifier.as_str()) { - generated_nodes += 1; - inputs.push(NodeInput::value(TaggedValue::None, false)); - convert_node_identifier - } else { - passthrough_node.clone() - }; - let mut original_location = OriginalLocation::default(); - original_location.auto_convert_index = Some(i); - DocumentNode { - inputs, - implementation: DocumentNodeImplementation::ProtoNode(proto_node), - visible: true, - original_location, - ..Default::default() + let inputs: Vec<_> = node_inputs(fields, first_node_io); + let input_count = inputs.len(); + let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); + + let passthrough_node = ops::passthrough::IDENTIFIER; + + let mut generated_nodes = 0; + let mut nodes: HashMap<_, _, _> = node_io_types + .iter() + .take(input_count) + .enumerate() + .map(|(i, inputs)| { + ( + NodeId(i as u64), + match inputs.len() { + 1 => { + let input = inputs.iter().next().unwrap(); + let input_ty = input.nested_type(); + let mut inputs = vec![NodeInput::import(input.clone(), i)]; + + let into_node_identifier = ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::IntoNode<{}>", input_ty.identifier_name())); + let convert_node_identifier = ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::ConvertNode<{}>", input_ty.identifier_name())); + + let proto_node = if into_node_registry.keys().any(|ident: &ProtoNodeIdentifier| ident.as_str() == into_node_identifier.as_str()) { + generated_nodes += 1; + into_node_identifier + } else if into_node_registry.keys().any(|ident| ident.as_str() == convert_node_identifier.as_str()) { + generated_nodes += 1; + inputs.push(NodeInput::value(TaggedValue::None, false)); + convert_node_identifier + } else { + passthrough_node.clone() + }; + let mut original_location = OriginalLocation::default(); + original_location.auto_convert_index = Some(i); + DocumentNode { + inputs, + implementation: DocumentNodeImplementation::ProtoNode(proto_node), + visible: true, + original_location, + ..Default::default() + } } - } - _ => DocumentNode { - inputs: vec![NodeInput::import(generic!(X), i)], - implementation: DocumentNodeImplementation::ProtoNode(passthrough_node.clone()), - visible: false, - ..Default::default() + _ => DocumentNode { + inputs: vec![NodeInput::import(generic!(X), i)], + implementation: DocumentNodeImplementation::ProtoNode(passthrough_node.clone()), + visible: false, + ..Default::default() + }, }, - }, - ) - }) - .collect(); + ) + }) + .collect(); - if generated_nodes == 0 && !memoize { - continue; - } + if generated_nodes == 0 && !memoize && !inject_scope { + continue; + } - let document_node = DocumentNode { - inputs: network_inputs, - call_argument: input_type.clone(), - implementation: DocumentNodeImplementation::ProtoNode(id.clone()), - visible: true, - skip_deduplication: false, - context_features: ContextDependencies::from(metadata.context_features.as_slice()), - ..Default::default() - }; - - nodes.insert(NodeId(input_count as u64), document_node); - - // If memoize is requested, append a Memoize node after the main node and redirect the export through it - let export_node_id = if *memoize { - let memoize_node_id = NodeId(input_count as u64 + 1); - let memoize_node = DocumentNode { - inputs: vec![NodeInput::node(NodeId(input_count as u64), 0)], - implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memoize::IDENTIFIER.clone()), + let document_node = DocumentNode { + inputs: network_inputs, + call_argument: input_type.clone(), + implementation: DocumentNodeImplementation::ProtoNode(id.clone()), visible: true, + skip_deduplication: false, + context_features: ContextDependencies::from(metadata.context_features.as_slice()), ..Default::default() }; - nodes.insert(memoize_node_id, memoize_node); - memoize_node_id - } else { - NodeId(input_count as u64) - }; - - let node = DocumentNode { - inputs, - call_argument: input_type.clone(), - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::Node { - node_id: export_node_id, - output_index: 0, - }], - nodes, - scope_injections: Default::default(), - generated: true, - }), - visible: true, - skip_deduplication: false, - ..Default::default() - }; - - custom.insert(id.clone(), node); - } - custom + nodes.insert(NodeId(input_count as u64), document_node); + + // If memoize is requested, append a Memoize node after the main node and redirect the export through it + let export_node_id = if *memoize { + let memoize_node_id = NodeId(input_count as u64 + 1); + let memoize_node = DocumentNode { + inputs: vec![NodeInput::node(NodeId(input_count as u64), 0)], + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memoize::IDENTIFIER.clone()), + visible: true, + ..Default::default() + }; + nodes.insert(memoize_node_id, memoize_node); + memoize_node_id + } else { + NodeId(input_count as u64) + }; + + let node = DocumentNode { + inputs, + call_argument: input_type.clone(), + implementation: DocumentNodeImplementation::Network(NodeNetwork { + exports: vec![NodeInput::Node { + node_id: export_node_id, + output_index: 0, + }], + nodes, + scope_injections: Default::default(), + generated: true, + }), + visible: true, + skip_deduplication: false, + ..Default::default() + }; + + substitutions.insert(id.clone(), node); + + // If `inject_scope` is requested, prepare the proto node template and type info needed + if *inject_scope + && let Some(implementations) = node_registry.get(&id) + && let Some((_, node_io)) = implementations.first() + { + let template = DocumentNode { + inputs: node_inputs(fields, node_io), + 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())); + } + } + + Self { substitutions, inject_scopes } + } } pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTypes) -> Vec { @@ -229,7 +271,7 @@ pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTyp }; let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; - match field.value_source { + match &field.value_source { RegistryValueSource::None => {} RegistryValueSource::Default(data) => { if let Some(custom_default) = TaggedValue::from_primitive_string(data, ty) { @@ -239,7 +281,7 @@ pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTyp warn!("Failed to parse default value for type `{ty:?}` with data `{data}`"); } } - RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), + RegistryValueSource::Scope(data) => return NodeInput::scope(*data), }; if let Some(type_default) = TaggedValue::from_type(ty) { diff --git a/tools/node-docs/src/page_node.rs b/tools/node-docs/src/page_node.rs index dabef2ff0c..e4a9962020 100644 --- a/tools/node-docs/src/page_node.rs +++ b/tools/node-docs/src/page_node.rs @@ -159,7 +159,7 @@ fn write_inputs(page: &mut std::fs::File, valid_input_types: &[Vec Some(default_value.to_string().replace(" :: ", "::")), _ => field .default_type