Skip to content

Commit 59f3835

Browse files
authored
Shaders: node macro (#2923)
* node_macro: cleanup attr parsing * node_macro: add `cfg()` attr to feature gate node impl * node_macro: add `shader_nodes` option * node_macro: fixup tests
1 parent 2d11d96 commit 59f3835

File tree

7 files changed

+101
-9
lines changed

7 files changed

+101
-9
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ tinyvec = { version = "1", features = ["std"] }
154154
criterion = { version = "0.5", features = ["html_reports"] }
155155
iai-callgrind = { version = "0.12.3" }
156156
ndarray = "0.16.1"
157+
strum = { version = "0.26.3", features = ["derive"] }
157158

158159
[profile.dev]
159160
opt-level = 1

node-graph/node-macro/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ syn = { workspace = true }
1919
proc-macro2 = { workspace = true }
2020
quote = { workspace = true }
2121
convert_case = { workspace = true }
22+
strum = { workspace = true }
2223

2324
indoc = "2.0.5"
2425
proc-macro-crate = "3.1.0"

node-graph/node-macro/src/codegen.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,30 +345,35 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
345345

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

348-
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier);
348+
let cfg = crate::shader_nodes::modify_cfg(&attributes);
349+
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier, &cfg);
349350
Ok(quote! {
350351
/// Underlying implementation for [#struct_name]
351352
#[inline]
352353
#[allow(clippy::too_many_arguments)]
353354
#vis #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
354355

356+
#cfg
355357
#[automatically_derived]
356358
impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
357359
#struct_where_clause
358360
{
359361
#eval_impl
360362
}
361363

364+
#cfg
362365
const fn #identifier() -> #graphene_core::ProtoNodeIdentifier {
363366
#graphene_core::ProtoNodeIdentifier::new(std::concat!(#identifier_path, "::", std::stringify!(#struct_name)))
364367
}
365368

369+
#cfg
366370
#[doc(inline)]
367371
pub use #mod_name::#struct_name;
368372

369373
#[doc(hidden)]
370374
#node_input_accessor
371375

376+
#cfg
372377
#[doc(hidden)]
373378
mod #mod_name {
374379
use super::*;
@@ -434,7 +439,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
434439
}
435440

436441
/// Generates strongly typed utilites to access inputs
437-
fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &Ident) -> TokenStream2 {
442+
fn generate_node_input_references(
443+
parsed: &ParsedNodeFn,
444+
fn_generics: &[crate::GenericParam],
445+
field_idents: &[&PatIdent],
446+
graphene_core: &TokenStream2,
447+
identifier: &Ident,
448+
cfg: &TokenStream2,
449+
) -> TokenStream2 {
438450
let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake));
439451

440452
let mut generated_input_accessor = Vec::new();
@@ -479,6 +491,7 @@ fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::G
479491
}
480492

481493
quote! {
494+
#cfg
482495
pub mod #inputs_module_name {
483496
use super::*;
484497

node-graph/node-macro/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use syn::GenericParam;
55
mod codegen;
66
mod derive_choice_type;
77
mod parsing;
8+
mod shader_nodes;
89
mod validation;
910

1011
/// Used to create a node definition.

node-graph/node-macro/src/parsing.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use syn::{
1212
};
1313

1414
use crate::codegen::generate_node_code;
15+
use crate::shader_nodes::ShaderNodeType;
1516

1617
#[derive(Debug)]
1718
pub(crate) struct Implementation {
@@ -45,6 +46,10 @@ pub(crate) struct NodeFnAttributes {
4546
pub(crate) path: Option<Path>,
4647
pub(crate) skip_impl: bool,
4748
pub(crate) properties_string: Option<LitStr>,
49+
/// whether to `#[cfg]` gate the node implementation, defaults to None
50+
pub(crate) cfg: Option<TokenStream2>,
51+
/// if this node should get a gpu implementation, defaults to None
52+
pub(crate) shader_node: Option<ShaderNodeType>,
4853
// Add more attributes as needed
4954
}
5055

@@ -184,15 +189,19 @@ impl Parse for NodeFnAttributes {
184189
let mut path = None;
185190
let mut skip_impl = false;
186191
let mut properties_string = None;
192+
let mut cfg = None;
193+
let mut shader_node = None;
187194

188195
let content = input;
189196
// let content;
190197
// syn::parenthesized!(content in input);
191198

192199
let nested = content.call(Punctuated::<Meta, Comma>::parse_terminated)?;
193200
for meta in nested {
194-
match meta {
195-
Meta::List(meta) if meta.path.is_ident("category") => {
201+
let name = meta.path().get_ident().ok_or_else(|| Error::new_spanned(meta.path(), "Node macro expects a known Ident, not a path"))?;
202+
match name.to_string().as_str() {
203+
"category" => {
204+
let meta = meta.require_list()?;
196205
if category.is_some() {
197206
return Err(Error::new_spanned(meta, "Multiple 'category' attributes are not allowed"));
198207
}
@@ -201,14 +210,16 @@ impl Parse for NodeFnAttributes {
201210
.map_err(|_| Error::new_spanned(meta, "Expected a string literal for 'category', e.g., category(\"Value\")"))?;
202211
category = Some(lit);
203212
}
204-
Meta::List(meta) if meta.path.is_ident("name") => {
213+
"name" => {
214+
let meta = meta.require_list()?;
205215
if display_name.is_some() {
206216
return Err(Error::new_spanned(meta, "Multiple 'name' attributes are not allowed"));
207217
}
208218
let parsed_name: LitStr = meta.parse_args().map_err(|_| Error::new_spanned(meta, "Expected a string for 'name', e.g., name(\"Memoize\")"))?;
209219
display_name = Some(parsed_name);
210220
}
211-
Meta::List(meta) if meta.path.is_ident("path") => {
221+
"path" => {
222+
let meta = meta.require_list()?;
212223
if path.is_some() {
213224
return Err(Error::new_spanned(meta, "Multiple 'path' attributes are not allowed"));
214225
}
@@ -217,13 +228,15 @@ impl Parse for NodeFnAttributes {
217228
.map_err(|_| Error::new_spanned(meta, "Expected a valid path for 'path', e.g., path(crate::MemoizeNode)"))?;
218229
path = Some(parsed_path);
219230
}
220-
Meta::Path(path) if path.is_ident("skip_impl") => {
231+
"skip_impl" => {
232+
let path = meta.require_path_only()?;
221233
if skip_impl {
222234
return Err(Error::new_spanned(path, "Multiple 'skip_impl' attributes are not allowed"));
223235
}
224236
skip_impl = true;
225237
}
226-
Meta::List(meta) if meta.path.is_ident("properties") => {
238+
"properties" => {
239+
let meta = meta.require_list()?;
227240
if properties_string.is_some() {
228241
return Err(Error::new_spanned(path, "Multiple 'properties_string' attributes are not allowed"));
229242
}
@@ -233,13 +246,27 @@ impl Parse for NodeFnAttributes {
233246

234247
properties_string = Some(parsed_properties_string);
235248
}
249+
"cfg" => {
250+
if cfg.is_some() {
251+
return Err(Error::new_spanned(path, "Multiple 'feature' attributes are not allowed"));
252+
}
253+
let meta = meta.require_list()?;
254+
cfg = Some(meta.tokens.clone());
255+
}
256+
"shader_node" => {
257+
if shader_node.is_some() {
258+
return Err(Error::new_spanned(path, "Multiple 'feature' attributes are not allowed"));
259+
}
260+
let meta = meta.require_list()?;
261+
shader_node = Some(syn::parse2(meta.tokens.to_token_stream())?);
262+
}
236263
_ => {
237264
return Err(Error::new_spanned(
238265
meta,
239266
indoc!(
240267
r#"
241268
Unsupported attribute in `node`.
242-
Supported attributes are 'category', 'path' and 'name'.
269+
Supported attributes are 'category', 'path' 'name', 'skip_impl', 'cfg' and 'properties'.
243270
244271
Example usage:
245272
#[node_macro::node(category("Value"), name("Test Node"))]
@@ -256,6 +283,8 @@ impl Parse for NodeFnAttributes {
256283
path,
257284
skip_impl,
258285
properties_string,
286+
cfg,
287+
shader_node,
259288
})
260289
}
261290
}
@@ -758,6 +787,8 @@ mod tests {
758787
path: Some(parse_quote!(graphene_core::TestNode)),
759788
skip_impl: true,
760789
properties_string: None,
790+
cfg: None,
791+
shader_node: None,
761792
},
762793
fn_name: Ident::new("add", Span::call_site()),
763794
struct_name: Ident::new("Add", Span::call_site()),
@@ -819,6 +850,8 @@ mod tests {
819850
path: None,
820851
skip_impl: false,
821852
properties_string: None,
853+
cfg: None,
854+
shader_node: None,
822855
},
823856
fn_name: Ident::new("transform", Span::call_site()),
824857
struct_name: Ident::new("Transform", Span::call_site()),
@@ -891,6 +924,8 @@ mod tests {
891924
path: None,
892925
skip_impl: false,
893926
properties_string: None,
927+
cfg: None,
928+
shader_node: None,
894929
},
895930
fn_name: Ident::new("circle", Span::call_site()),
896931
struct_name: Ident::new("Circle", Span::call_site()),
@@ -948,6 +983,8 @@ mod tests {
948983
path: None,
949984
skip_impl: false,
950985
properties_string: None,
986+
cfg: None,
987+
shader_node: None,
951988
},
952989
fn_name: Ident::new("levels", Span::call_site()),
953990
struct_name: Ident::new("Levels", Span::call_site()),
@@ -1017,6 +1054,8 @@ mod tests {
10171054
path: Some(parse_quote!(graphene_core::TestNode)),
10181055
skip_impl: false,
10191056
properties_string: None,
1057+
cfg: None,
1058+
shader_node: None,
10201059
},
10211060
fn_name: Ident::new("add", Span::call_site()),
10221061
struct_name: Ident::new("Add", Span::call_site()),
@@ -1074,6 +1113,8 @@ mod tests {
10741113
path: None,
10751114
skip_impl: false,
10761115
properties_string: None,
1116+
cfg: None,
1117+
shader_node: None,
10771118
},
10781119
fn_name: Ident::new("load_image", Span::call_site()),
10791120
struct_name: Ident::new("LoadImage", Span::call_site()),
@@ -1131,6 +1172,8 @@ mod tests {
11311172
path: None,
11321173
skip_impl: false,
11331174
properties_string: None,
1175+
cfg: None,
1176+
shader_node: None,
11341177
},
11351178
fn_name: Ident::new("custom_node", Span::call_site()),
11361179
struct_name: Ident::new("CustomNode", Span::call_site()),
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use crate::parsing::NodeFnAttributes;
2+
use proc_macro2::{Ident, TokenStream};
3+
use quote::quote;
4+
use strum::{EnumString, VariantNames};
5+
use syn::Error;
6+
use syn::parse::{Parse, ParseStream};
7+
8+
pub const STD_FEATURE_GATE: &str = "std";
9+
10+
pub fn modify_cfg(attributes: &NodeFnAttributes) -> TokenStream {
11+
match (&attributes.cfg, &attributes.shader_node) {
12+
(Some(cfg), Some(_)) => quote!(#[cfg(all(#cfg, feature = #STD_FEATURE_GATE))]),
13+
(Some(cfg), None) => quote!(#[cfg(#cfg)]),
14+
(None, Some(_)) => quote!(#[cfg(feature = #STD_FEATURE_GATE)]),
15+
(None, None) => quote!(),
16+
}
17+
}
18+
19+
#[derive(Debug, EnumString, VariantNames)]
20+
pub(crate) enum ShaderNodeType {
21+
PerPixelAdjust,
22+
}
23+
24+
impl Parse for ShaderNodeType {
25+
fn parse(input: ParseStream) -> syn::Result<Self> {
26+
let ident: Ident = input.parse()?;
27+
Ok(match ident.to_string().as_str() {
28+
"PerPixelAdjust" => ShaderNodeType::PerPixelAdjust,
29+
_ => return Err(Error::new_spanned(&ident, format!("attr 'shader_node' must be one of {:?}", Self::VARIANTS))),
30+
})
31+
}
32+
}

0 commit comments

Comments
 (0)