From a63c8c55a578776239b572d179c3cbdac3fe0ba0 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 23 Jul 2025 11:37:11 +0200 Subject: [PATCH 1/4] node_macro: cleanup attr parsing --- node-graph/node-macro/src/parsing.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 81151110a9..fce269aae5 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -191,8 +191,10 @@ impl Parse for NodeFnAttributes { let nested = content.call(Punctuated::::parse_terminated)?; for meta in nested { - match meta { - Meta::List(meta) if meta.path.is_ident("category") => { + let name = meta.path().get_ident().ok_or_else(|| Error::new_spanned(meta.path(), "Node macro expects a known Ident, not a path"))?; + match name.to_string().as_str() { + "category" => { + let meta = meta.require_list()?; if category.is_some() { return Err(Error::new_spanned(meta, "Multiple 'category' attributes are not allowed")); } @@ -201,14 +203,16 @@ impl Parse for NodeFnAttributes { .map_err(|_| Error::new_spanned(meta, "Expected a string literal for 'category', e.g., category(\"Value\")"))?; category = Some(lit); } - Meta::List(meta) if meta.path.is_ident("name") => { + "name" => { + let meta = meta.require_list()?; if display_name.is_some() { return Err(Error::new_spanned(meta, "Multiple 'name' attributes are not allowed")); } let parsed_name: LitStr = meta.parse_args().map_err(|_| Error::new_spanned(meta, "Expected a string for 'name', e.g., name(\"Memoize\")"))?; display_name = Some(parsed_name); } - Meta::List(meta) if meta.path.is_ident("path") => { + "path" => { + let meta = meta.require_list()?; if path.is_some() { return Err(Error::new_spanned(meta, "Multiple 'path' attributes are not allowed")); } @@ -217,13 +221,15 @@ impl Parse for NodeFnAttributes { .map_err(|_| Error::new_spanned(meta, "Expected a valid path for 'path', e.g., path(crate::MemoizeNode)"))?; path = Some(parsed_path); } - Meta::Path(path) if path.is_ident("skip_impl") => { + "skip_impl" => { + let path = meta.require_path_only()?; if skip_impl { return Err(Error::new_spanned(path, "Multiple 'skip_impl' attributes are not allowed")); } skip_impl = true; } - Meta::List(meta) if meta.path.is_ident("properties") => { + "properties" => { + let meta = meta.require_list()?; if properties_string.is_some() { return Err(Error::new_spanned(path, "Multiple 'properties_string' attributes are not allowed")); } @@ -239,7 +245,7 @@ impl Parse for NodeFnAttributes { indoc!( r#" Unsupported attribute in `node`. - Supported attributes are 'category', 'path' and 'name'. + Supported attributes are 'category', 'path' 'name', 'skip_impl' and 'properties'. Example usage: #[node_macro::node(category("Value"), name("Test Node"))] From a1a0b76ac3b57e055af922e5c17a0a61954f390e Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 23 Jul 2025 11:54:13 +0200 Subject: [PATCH 2/4] node_macro: add `cfg()` attr to feature gate node impl --- node-graph/node-macro/src/codegen.rs | 17 +++++++++++++++-- node-graph/node-macro/src/parsing.rs | 13 ++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 456f354530..9c05b03748 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -345,13 +345,15 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body + #cfg #[automatically_derived] impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> #struct_where_clause @@ -359,16 +361,19 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result #graphene_core::ProtoNodeIdentifier { #graphene_core::ProtoNodeIdentifier::new(std::concat!(#identifier_path, "::", std::stringify!(#struct_name))) } + #cfg #[doc(inline)] pub use #mod_name::#struct_name; #[doc(hidden)] #node_input_accessor + #cfg #[doc(hidden)] mod #mod_name { use super::*; @@ -434,7 +439,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result TokenStream2 { +fn generate_node_input_references( + parsed: &ParsedNodeFn, + fn_generics: &[crate::GenericParam], + field_idents: &[&PatIdent], + graphene_core: &TokenStream2, + identifier: &Ident, + cfg: &TokenStream2, +) -> TokenStream2 { let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake)); let mut generated_input_accessor = Vec::new(); @@ -479,6 +491,7 @@ fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::G } quote! { + #cfg pub mod #inputs_module_name { use super::*; diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index fce269aae5..5a83beafb3 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -45,6 +45,8 @@ pub(crate) struct NodeFnAttributes { pub(crate) path: Option, pub(crate) skip_impl: bool, pub(crate) properties_string: Option, + /// whether to `#[cfg]` gate the node implementation, defaults to None + pub(crate) cfg: Option, // Add more attributes as needed } @@ -184,6 +186,7 @@ impl Parse for NodeFnAttributes { let mut path = None; let mut skip_impl = false; let mut properties_string = None; + let mut cfg = None; let content = input; // let content; @@ -239,13 +242,20 @@ impl Parse for NodeFnAttributes { properties_string = Some(parsed_properties_string); } + "cfg" => { + if cfg.is_some() { + return Err(Error::new_spanned(path, "Multiple 'feature' attributes are not allowed")); + } + let meta = meta.require_list()?; + cfg = Some(meta.tokens.clone()); + } _ => { return Err(Error::new_spanned( meta, indoc!( r#" Unsupported attribute in `node`. - Supported attributes are 'category', 'path' 'name', 'skip_impl' and 'properties'. + Supported attributes are 'category', 'path' 'name', 'skip_impl', 'cfg' and 'properties'. Example usage: #[node_macro::node(category("Value"), name("Test Node"))] @@ -262,6 +272,7 @@ impl Parse for NodeFnAttributes { path, skip_impl, properties_string, + cfg, }) } } From 3dd10e05f32ef53fb7250498a362bf8db367c26f Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 23 Jul 2025 13:04:22 +0200 Subject: [PATCH 3/4] node_macro: add `shader_nodes` option --- Cargo.lock | 1 + Cargo.toml | 1 + node-graph/node-macro/Cargo.toml | 1 + node-graph/node-macro/src/codegen.rs | 2 +- node-graph/node-macro/src/lib.rs | 1 + node-graph/node-macro/src/parsing.rs | 12 +++++++++ node-graph/node-macro/src/shader_nodes.rs | 32 +++++++++++++++++++++++ 7 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 node-graph/node-macro/src/shader_nodes.rs diff --git a/Cargo.lock b/Cargo.lock index d63104125a..52dabd6b4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2889,6 +2889,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", + "strum", "syn 2.0.104", ] diff --git a/Cargo.toml b/Cargo.toml index cf2a1ec0f6..a999485205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,6 +154,7 @@ tinyvec = { version = "1", features = ["std"] } criterion = { version = "0.5", features = ["html_reports"] } iai-callgrind = { version = "0.12.3" } ndarray = "0.16.1" +strum = { version = "0.26.3", features = ["derive"] } [profile.dev] opt-level = 1 diff --git a/node-graph/node-macro/Cargo.toml b/node-graph/node-macro/Cargo.toml index ebc1037d92..464b4ddd0b 100644 --- a/node-graph/node-macro/Cargo.toml +++ b/node-graph/node-macro/Cargo.toml @@ -19,6 +19,7 @@ syn = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } convert_case = { workspace = true } +strum = { workspace = true } indoc = "2.0.5" proc-macro-crate = "3.1.0" diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 9c05b03748..a969460e1a 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -345,7 +345,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result, /// whether to `#[cfg]` gate the node implementation, defaults to None pub(crate) cfg: Option, + /// if this node should get a gpu implementation, defaults to None + pub(crate) shader_node: Option, // Add more attributes as needed } @@ -187,6 +190,7 @@ impl Parse for NodeFnAttributes { let mut skip_impl = false; let mut properties_string = None; let mut cfg = None; + let mut shader_node = None; let content = input; // let content; @@ -249,6 +253,13 @@ impl Parse for NodeFnAttributes { let meta = meta.require_list()?; cfg = Some(meta.tokens.clone()); } + "shader_node" => { + if shader_node.is_some() { + return Err(Error::new_spanned(path, "Multiple 'feature' attributes are not allowed")); + } + let meta = meta.require_list()?; + shader_node = Some(syn::parse2(meta.tokens.to_token_stream())?); + } _ => { return Err(Error::new_spanned( meta, @@ -273,6 +284,7 @@ impl Parse for NodeFnAttributes { skip_impl, properties_string, cfg, + shader_node, }) } } diff --git a/node-graph/node-macro/src/shader_nodes.rs b/node-graph/node-macro/src/shader_nodes.rs new file mode 100644 index 0000000000..919d3ef878 --- /dev/null +++ b/node-graph/node-macro/src/shader_nodes.rs @@ -0,0 +1,32 @@ +use crate::parsing::NodeFnAttributes; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use strum::{EnumString, VariantNames}; +use syn::Error; +use syn::parse::{Parse, ParseStream}; + +pub const STD_FEATURE_GATE: &str = "std"; + +pub fn modify_cfg(attributes: &NodeFnAttributes) -> TokenStream { + match (&attributes.cfg, &attributes.shader_node) { + (Some(cfg), Some(_)) => quote!(#[cfg(all(#cfg, feature = #STD_FEATURE_GATE))]), + (Some(cfg), None) => quote!(#[cfg(#cfg)]), + (None, Some(_)) => quote!(#[cfg(feature = #STD_FEATURE_GATE)]), + (None, None) => quote!(), + } +} + +#[derive(Debug, EnumString, VariantNames)] +pub(crate) enum ShaderNodeType { + PerPixelAdjust, +} + +impl Parse for ShaderNodeType { + fn parse(input: ParseStream) -> syn::Result { + let ident: Ident = input.parse()?; + Ok(match ident.to_string().as_str() { + "PerPixelAdjust" => ShaderNodeType::PerPixelAdjust, + _ => return Err(Error::new_spanned(&ident, format!("attr 'shader_node' must be one of {:?}", Self::VARIANTS))), + }) + } +} From e0bcd16207c47206cea38a646e83f47b6b42fae8 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 23 Jul 2025 16:03:06 +0200 Subject: [PATCH 4/4] node_macro: fixup tests --- node-graph/node-macro/src/parsing.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 782c4ecf4a..fa80828576 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -787,6 +787,8 @@ mod tests { path: Some(parse_quote!(graphene_core::TestNode)), skip_impl: true, properties_string: None, + cfg: None, + shader_node: None, }, fn_name: Ident::new("add", Span::call_site()), struct_name: Ident::new("Add", Span::call_site()), @@ -848,6 +850,8 @@ mod tests { path: None, skip_impl: false, properties_string: None, + cfg: None, + shader_node: None, }, fn_name: Ident::new("transform", Span::call_site()), struct_name: Ident::new("Transform", Span::call_site()), @@ -920,6 +924,8 @@ mod tests { path: None, skip_impl: false, properties_string: None, + cfg: None, + shader_node: None, }, fn_name: Ident::new("circle", Span::call_site()), struct_name: Ident::new("Circle", Span::call_site()), @@ -977,6 +983,8 @@ mod tests { path: None, skip_impl: false, properties_string: None, + cfg: None, + shader_node: None, }, fn_name: Ident::new("levels", Span::call_site()), struct_name: Ident::new("Levels", Span::call_site()), @@ -1046,6 +1054,8 @@ mod tests { path: Some(parse_quote!(graphene_core::TestNode)), skip_impl: false, properties_string: None, + cfg: None, + shader_node: None, }, fn_name: Ident::new("add", Span::call_site()), struct_name: Ident::new("Add", Span::call_site()), @@ -1103,6 +1113,8 @@ mod tests { path: None, skip_impl: false, properties_string: None, + cfg: None, + shader_node: None, }, fn_name: Ident::new("load_image", Span::call_site()), struct_name: Ident::new("LoadImage", Span::call_site()), @@ -1160,6 +1172,8 @@ mod tests { path: None, skip_impl: false, properties_string: None, + cfg: None, + shader_node: None, }, fn_name: Ident::new("custom_node", Span::call_site()), struct_name: Ident::new("CustomNode", Span::call_site()),