mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 02:18:11 +08:00
Add Destruct macro and trait for automatically acessing struct fields
This commit is contained in:
+1
@@ -36,6 +36,7 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
|
|||||||
description,
|
description,
|
||||||
properties,
|
properties,
|
||||||
context_features,
|
context_features,
|
||||||
|
output_fields: _,
|
||||||
} = metadata;
|
} = metadata;
|
||||||
|
|
||||||
let Some(implementations) = &node_registry.get(id) else { continue };
|
let Some(implementations) = &node_registry.get(id) else { continue };
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub struct NodeMetadata {
|
|||||||
pub description: &'static str,
|
pub description: &'static str,
|
||||||
pub properties: Option<&'static str>,
|
pub properties: Option<&'static str>,
|
||||||
pub context_features: Vec<ContextFeature>,
|
pub context_features: Vec<ContextFeature>,
|
||||||
|
pub output_fields: &'static [StructField],
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translation struct between macro and definition
|
// Translation struct between macro and definition
|
||||||
@@ -36,6 +37,26 @@ pub struct FieldMetadata {
|
|||||||
pub unit: Option<&'static str>,
|
pub unit: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct StructField {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub node_path: &'static str,
|
||||||
|
pub ty: Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Destruct {
|
||||||
|
fn fields(&self) -> &'static [StructField];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Destruct for &T
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
fn fields(&self) -> &'static [StructField] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum RegistryWidgetOverride {
|
pub enum RegistryWidgetOverride {
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -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 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 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 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()))?;
|
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,
|
description: #description,
|
||||||
properties: #properties,
|
properties: #properties,
|
||||||
context_features: vec![#(ContextFeature::#context_features,)*],
|
context_features: vec![#(ContextFeature::#context_features,)*],
|
||||||
|
output_fields: #output_fields,
|
||||||
fields: vec![
|
fields: vec![
|
||||||
#(
|
#(
|
||||||
FieldMetadata {
|
FieldMetadata {
|
||||||
|
|||||||
@@ -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] {
|
||||||
|
&[
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ mod buffer_struct;
|
|||||||
mod codegen;
|
mod codegen;
|
||||||
mod crate_ident;
|
mod crate_ident;
|
||||||
mod derive_choice_type;
|
mod derive_choice_type;
|
||||||
|
mod destruct;
|
||||||
mod parsing;
|
mod parsing;
|
||||||
mod shader_nodes;
|
mod shader_nodes;
|
||||||
mod validation;
|
mod validation;
|
||||||
@@ -39,3 +40,16 @@ pub fn derive_buffer_struct(input_item: TokenStream) -> TokenStream {
|
|||||||
let crate_ident = CrateIdent::default();
|
let crate_ident = CrateIdent::default();
|
||||||
TokenStream::from(buffer_struct::derive_buffer_struct(&crate_ident, input_item).unwrap_or_else(|err| err.to_compile_error()))
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ pub(crate) struct NodeFnAttributes {
|
|||||||
pub(crate) shader_node: Option<ShaderNodeType>,
|
pub(crate) shader_node: Option<ShaderNodeType>,
|
||||||
/// Custom serialization function path (e.g., "my_module::custom_serialize")
|
/// Custom serialization function path (e.g., "my_module::custom_serialize")
|
||||||
pub(crate) serialize: Option<Path>,
|
pub(crate) serialize: Option<Path>,
|
||||||
|
pub(crate) deconstruct_output: bool,
|
||||||
// Add more attributes as needed
|
// Add more attributes as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +202,7 @@ impl Parse for NodeFnAttributes {
|
|||||||
let mut display_name = None;
|
let mut display_name = None;
|
||||||
let mut path = None;
|
let mut path = None;
|
||||||
let mut skip_impl = false;
|
let mut skip_impl = false;
|
||||||
|
let mut deconstruct_output = false;
|
||||||
let mut properties_string = None;
|
let mut properties_string = None;
|
||||||
let mut cfg = None;
|
let mut cfg = None;
|
||||||
let mut shader_node = None;
|
let mut shader_node = None;
|
||||||
@@ -271,6 +273,17 @@ impl Parse for NodeFnAttributes {
|
|||||||
}
|
}
|
||||||
skip_impl = true;
|
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.
|
// 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.
|
// 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!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Unsupported attribute in `node`.
|
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:
|
Example usage:
|
||||||
#[node_macro::node(..., name("Test Node"), ...)]
|
#[node_macro::node(..., name("Test Node"), ...)]
|
||||||
"#
|
"#
|
||||||
@@ -361,6 +374,7 @@ impl Parse for NodeFnAttributes {
|
|||||||
cfg,
|
cfg,
|
||||||
shader_node,
|
shader_node,
|
||||||
serialize,
|
serialize,
|
||||||
|
deconstruct_output,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -934,6 +948,7 @@ mod tests {
|
|||||||
cfg: None,
|
cfg: None,
|
||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
|
deconstruct_output: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("add", Span::call_site()),
|
fn_name: Ident::new("add", Span::call_site()),
|
||||||
struct_name: Ident::new("Add", Span::call_site()),
|
struct_name: Ident::new("Add", Span::call_site()),
|
||||||
@@ -1002,6 +1017,7 @@ mod tests {
|
|||||||
cfg: None,
|
cfg: None,
|
||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
|
deconstruct_output: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("transform", Span::call_site()),
|
fn_name: Ident::new("transform", Span::call_site()),
|
||||||
struct_name: Ident::new("Transform", Span::call_site()),
|
struct_name: Ident::new("Transform", Span::call_site()),
|
||||||
@@ -1084,6 +1100,7 @@ mod tests {
|
|||||||
cfg: None,
|
cfg: None,
|
||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
|
deconstruct_output: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("circle", Span::call_site()),
|
fn_name: Ident::new("circle", Span::call_site()),
|
||||||
struct_name: Ident::new("Circle", Span::call_site()),
|
struct_name: Ident::new("Circle", Span::call_site()),
|
||||||
@@ -1148,6 +1165,7 @@ mod tests {
|
|||||||
cfg: None,
|
cfg: None,
|
||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
|
deconstruct_output: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("levels", Span::call_site()),
|
fn_name: Ident::new("levels", Span::call_site()),
|
||||||
struct_name: Ident::new("Levels", Span::call_site()),
|
struct_name: Ident::new("Levels", Span::call_site()),
|
||||||
@@ -1224,6 +1242,7 @@ mod tests {
|
|||||||
cfg: None,
|
cfg: None,
|
||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
|
deconstruct_output: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("add", Span::call_site()),
|
fn_name: Ident::new("add", Span::call_site()),
|
||||||
struct_name: Ident::new("Add", Span::call_site()),
|
struct_name: Ident::new("Add", Span::call_site()),
|
||||||
@@ -1288,6 +1307,7 @@ mod tests {
|
|||||||
cfg: None,
|
cfg: None,
|
||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
|
deconstruct_output: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("load_image", Span::call_site()),
|
fn_name: Ident::new("load_image", Span::call_site()),
|
||||||
struct_name: Ident::new("LoadImage", Span::call_site()),
|
struct_name: Ident::new("LoadImage", Span::call_site()),
|
||||||
@@ -1352,6 +1372,7 @@ mod tests {
|
|||||||
cfg: None,
|
cfg: None,
|
||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
|
deconstruct_output: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("custom_node", Span::call_site()),
|
fn_name: Ident::new("custom_node", Span::call_site()),
|
||||||
struct_name: Ident::new("CustomNode", Span::call_site()),
|
struct_name: Ident::new("CustomNode", Span::call_site()),
|
||||||
|
|||||||
Reference in New Issue
Block a user