Add Destruct macro and trait for automatically acessing struct fields

This commit is contained in:
Dennis Kobert
2025-04-14 08:15:26 +02:00
parent 0531769c41
commit 2e97e81290
6 changed files with 126 additions and 1 deletions

View File

@@ -402,6 +402,11 @@ 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 output_fields = match attributes.deconstruct_output {
false => quote!(&[]),
true => quote!(#output_type::fields),
};
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);
let ShaderTokens { shader_entry_point, gpu_node } = attributes.shader_node.as_ref().map(|n| n.codegen(crate_ident, parsed)).unwrap_or(Ok(ShaderTokens::default()))?;
@@ -474,6 +479,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
description: #description,
properties: #properties,
context_features: vec![#(ContextFeature::#context_features,)*],
output_fields: #output_fields,
fields: vec![
#(
FieldMetadata {

View File

@@ -0,0 +1,62 @@
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{Error, Ident, spanned::Spanned};
pub fn derive(struct_name: Ident, data: syn::Data) -> syn::Result<TokenStream2> {
let syn::Data::Struct(data_struct) = data else {
return Err(Error::new(proc_macro2::Span::call_site(), String::from("Deriving `Destruct` is currently only supported for structs")));
};
let found_crate = proc_macro_crate::crate_name("graphene-core").map_err(|e| {
Error::new(
proc_macro2::Span::call_site(),
format!("Failed to find location of graphene_core. Make sure it is imported as a dependency: {}", e),
)
})?;
let crate_name = match found_crate {
proc_macro_crate::FoundCrate::Itself => quote!(crate),
proc_macro_crate::FoundCrate::Name(name) => {
let ident = format_ident!("{}", name);
quote!(#ident)
}
};
let path = quote!(std::module_path!().rsplit_once("::").unwrap().0);
let mut node_implementations = Vec::with_capacity(data_struct.fields.len());
let mut field_structs = Vec::with_capacity(data_struct.fields.len());
for field in data_struct.fields {
let Some(field_name) = field.ident else {
return Err(Error::new(field.span(), String::from("Destruct cant be used on tuple structs")));
};
let ty = field.ty;
let fn_name = quote::format_ident!("extract_ {field_name}");
node_implementations.push(quote! {
#[node_macro(category(""))]
fn #fn_name(_: impl Ctx, data: #struct_name) -> #ty {
data.#field_name
}
});
field_structs.push(quote! {
#crate_name::registry::FieldStruct {
name: stringify!(#field_name),
node_path: concat!()
}
})
}
Ok(quote! {
impl graphene_core::registry::Destruct for #struct_name {
fn fields() -> &[graphene_core::registry::FieldStruct] {
&[
]
}
}
})
}

View File

@@ -7,6 +7,7 @@ mod buffer_struct;
mod codegen;
mod crate_ident;
mod derive_choice_type;
mod destruct;
mod parsing;
mod shader_nodes;
mod validation;
@@ -39,3 +40,16 @@ pub fn derive_buffer_struct(input_item: TokenStream) -> TokenStream {
let crate_ident = CrateIdent::default();
TokenStream::from(buffer_struct::derive_buffer_struct(&crate_ident, input_item).unwrap_or_else(|err| err.to_compile_error()))
}
#[proc_macro_error]
#[proc_macro_derive(Destruct)]
/// Derives the `Destruct` trait for structs and creates accessor node implementations.
pub fn derive_destruct(item: TokenStream) -> TokenStream {
let s = syn::parse_macro_input!(item as syn::DeriveInput);
let parse_result = destruct::derive(s.ident, s.data).into();
let Ok(parsed_node) = parse_result else {
let e = parse_result.unwrap_err();
return syn::Error::new(e.span(), format!("Failed to parse node function: {e}")).to_compile_error().into();
};
parsed_node.into()
}

View File

@@ -52,6 +52,7 @@ pub(crate) struct NodeFnAttributes {
pub(crate) shader_node: Option<ShaderNodeType>,
/// Custom serialization function path (e.g., "my_module::custom_serialize")
pub(crate) serialize: Option<Path>,
pub(crate) deconstruct_output: bool,
// Add more attributes as needed
}
@@ -201,6 +202,7 @@ impl Parse for NodeFnAttributes {
let mut display_name = None;
let mut path = None;
let mut skip_impl = false;
let mut deconstruct_output = false;
let mut properties_string = None;
let mut cfg = None;
let mut shader_node = None;
@@ -271,6 +273,17 @@ impl Parse for NodeFnAttributes {
}
skip_impl = true;
}
// Indicator that the node output should be deconstructed into its fields.
//
// Example usage:
// #[node_macro::node(..., deconstruct_output, ...)]
"deconstruct_output" => {
let path = meta.require_path_only()?;
if deconstruct_output {
return Err(Error::new_spanned(path, "Multiple 'deconstruct_output' attributes are not allowed"));
}
deconstruct_output = true;
}
// Override UI layout generator function name defined in `node_properties.rs` that returns a custom Properties panel layout for this node.
// This is used to create custom UI for the input parameters of the node in cases where the defaults generated from the type and attributes are insufficient.
//
@@ -329,7 +342,7 @@ impl Parse for NodeFnAttributes {
indoc!(
r#"
Unsupported attribute in `node`.
Supported attributes are 'category', 'name', 'path', 'skip_impl', 'properties', 'cfg', 'shader_node', and 'serialize'.
Supported attributes are 'category', 'name', 'path', 'skip_impl', 'deconstruct_output', 'properties', 'cfg', 'shader_node', and 'serialize'.
Example usage:
#[node_macro::node(..., name("Test Node"), ...)]
"#
@@ -361,6 +374,7 @@ impl Parse for NodeFnAttributes {
cfg,
shader_node,
serialize,
deconstruct_output,
})
}
}
@@ -934,6 +948,7 @@ mod tests {
cfg: None,
shader_node: None,
serialize: None,
deconstruct_output: false,
},
fn_name: Ident::new("add", Span::call_site()),
struct_name: Ident::new("Add", Span::call_site()),
@@ -1002,6 +1017,7 @@ mod tests {
cfg: None,
shader_node: None,
serialize: None,
deconstruct_output: false,
},
fn_name: Ident::new("transform", Span::call_site()),
struct_name: Ident::new("Transform", Span::call_site()),
@@ -1084,6 +1100,7 @@ mod tests {
cfg: None,
shader_node: None,
serialize: None,
deconstruct_output: false,
},
fn_name: Ident::new("circle", Span::call_site()),
struct_name: Ident::new("Circle", Span::call_site()),
@@ -1148,6 +1165,7 @@ mod tests {
cfg: None,
shader_node: None,
serialize: None,
deconstruct_output: false,
},
fn_name: Ident::new("levels", Span::call_site()),
struct_name: Ident::new("Levels", Span::call_site()),
@@ -1224,6 +1242,7 @@ mod tests {
cfg: None,
shader_node: None,
serialize: None,
deconstruct_output: false,
},
fn_name: Ident::new("add", Span::call_site()),
struct_name: Ident::new("Add", Span::call_site()),
@@ -1288,6 +1307,7 @@ mod tests {
cfg: None,
shader_node: None,
serialize: None,
deconstruct_output: false,
},
fn_name: Ident::new("load_image", Span::call_site()),
struct_name: Ident::new("LoadImage", Span::call_site()),
@@ -1352,6 +1372,7 @@ mod tests {
cfg: None,
shader_node: None,
serialize: None,
deconstruct_output: false,
},
fn_name: Ident::new("custom_node", Span::call_site()),
struct_name: Ident::new("CustomNode", Span::call_site()),