Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions node-graph/node-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
17 changes: 15 additions & 2 deletions node-graph/node-macro/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,30 +345,35 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre

let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));

let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier);
let cfg = crate::shader_nodes::modify_cfg(&attributes);
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier, &cfg);
Ok(quote! {
/// Underlying implementation for [#struct_name]
#[inline]
#[allow(clippy::too_many_arguments)]
#vis #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#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
{
#eval_impl
}

#cfg
const fn #identifier() -> #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::*;
Expand Down Expand Up @@ -434,7 +439,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
}

/// Generates strongly typed utilites to access inputs
fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &Ident) -> 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();
Expand Down Expand Up @@ -479,6 +491,7 @@ fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::G
}

quote! {
#cfg
pub mod #inputs_module_name {
use super::*;

Expand Down
1 change: 1 addition & 0 deletions node-graph/node-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use syn::GenericParam;
mod codegen;
mod derive_choice_type;
mod parsing;
mod shader_nodes;
mod validation;

/// Used to create a node definition.
Expand Down
57 changes: 50 additions & 7 deletions node-graph/node-macro/src/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use syn::{
};

use crate::codegen::generate_node_code;
use crate::shader_nodes::ShaderNodeType;

#[derive(Debug)]
pub(crate) struct Implementation {
Expand Down Expand Up @@ -45,6 +46,10 @@ pub(crate) struct NodeFnAttributes {
pub(crate) path: Option<Path>,
pub(crate) skip_impl: bool,
pub(crate) properties_string: Option<LitStr>,
/// whether to `#[cfg]` gate the node implementation, defaults to None
pub(crate) cfg: Option<TokenStream2>,
/// if this node should get a gpu implementation, defaults to None
pub(crate) shader_node: Option<ShaderNodeType>,
// Add more attributes as needed
}

Expand Down Expand Up @@ -184,15 +189,19 @@ impl Parse for NodeFnAttributes {
let mut path = None;
let mut skip_impl = false;
let mut properties_string = None;
let mut cfg = None;
let mut shader_node = None;

let content = input;
// let content;
// syn::parenthesized!(content in input);

let nested = content.call(Punctuated::<Meta, Comma>::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"));
}
Expand All @@ -201,14 +210,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"));
}
Expand All @@ -217,13 +228,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"));
}
Expand All @@ -233,13 +246,27 @@ 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());
}
"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,
indoc!(
r#"
Unsupported attribute in `node`.
Supported attributes are 'category', 'path' and 'name'.
Supported attributes are 'category', 'path' 'name', 'skip_impl', 'cfg' and 'properties'.

Example usage:
#[node_macro::node(category("Value"), name("Test Node"))]
Expand All @@ -256,6 +283,8 @@ impl Parse for NodeFnAttributes {
path,
skip_impl,
properties_string,
cfg,
shader_node,
})
}
}
Expand Down Expand Up @@ -758,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()),
Expand Down Expand Up @@ -819,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()),
Expand Down Expand Up @@ -891,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()),
Expand Down Expand Up @@ -948,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()),
Expand Down Expand Up @@ -1017,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()),
Expand Down Expand Up @@ -1074,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()),
Expand Down Expand Up @@ -1131,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()),
Expand Down
32 changes: 32 additions & 0 deletions node-graph/node-macro/src/shader_nodes.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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))),
})
}
}
Loading