mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 06:38:03 +08:00
Merge branch 'master' into fix-range
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
name = "node-macro"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.88"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
edition = "2024"
|
||||
readme = "../../README.md"
|
||||
@@ -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"
|
||||
@@ -26,4 +27,3 @@ proc-macro-error2 = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
graphene-core = { workspace = true }
|
||||
|
||||
|
||||
@@ -362,13 +362,15 @@ 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
|
||||
@@ -376,16 +378,19 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
#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::*;
|
||||
@@ -453,7 +458,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();
|
||||
@@ -498,6 +510,7 @@ fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::G
|
||||
}
|
||||
|
||||
quote! {
|
||||
#cfg
|
||||
pub mod #inputs_module_name {
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -111,13 +111,21 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let crate_name = proc_macro_crate::crate_name("graphene-core")
|
||||
.map_err(|e| syn::Error::new(Span::call_site(), format!("Failed to find location of graphene_core. Make sure it is imported as a dependency: {}", e)))?;
|
||||
let crate_name = match crate_name {
|
||||
proc_macro_crate::FoundCrate::Itself => quote!(crate),
|
||||
proc_macro_crate::FoundCrate::Name(name) => {
|
||||
let identifier = Ident::new(&name, Span::call_site());
|
||||
quote! { #identifier }
|
||||
let crate_name = {
|
||||
let crate_name = proc_macro_crate::crate_name("graphene-core-shaders")
|
||||
.or_else(|_e| proc_macro_crate::crate_name("graphene-core"))
|
||||
.map_err(|e| {
|
||||
syn::Error::new(
|
||||
Span::call_site(),
|
||||
format!("Failed to find location of 'graphene_core' or 'graphene-core-shaders'. Make sure it is imported as a dependency: {}", e),
|
||||
)
|
||||
})?;
|
||||
match crate_name {
|
||||
proc_macro_crate::FoundCrate::Itself => quote!(crate),
|
||||
proc_macro_crate::FoundCrate::Name(name) => {
|
||||
let identifier = Ident::new(&name, Span::call_site());
|
||||
quote! { #identifier }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -140,19 +148,19 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
|
||||
let docstring = match &variant.basic_item.description {
|
||||
Some(s) => {
|
||||
let s = s.trim();
|
||||
quote! { Some(::std::borrow::Cow::Borrowed(#s)) }
|
||||
quote! { Some(#s) }
|
||||
}
|
||||
None => quote! { None },
|
||||
};
|
||||
let icon = match &variant.basic_item.icon {
|
||||
Some(s) => quote! { Some(::std::borrow::Cow::Borrowed(#s)) },
|
||||
Some(s) => quote! { Some(#s) },
|
||||
None => quote! { None },
|
||||
};
|
||||
quote! {
|
||||
(
|
||||
#name::#vname, #crate_name::registry::VariantMetadata {
|
||||
name: ::std::borrow::Cow::Borrowed(#vname_str),
|
||||
label: ::std::borrow::Cow::Borrowed(#label),
|
||||
#name::#vname, #crate_name::choice_type::VariantMetadata {
|
||||
name: #vname_str,
|
||||
label: #label,
|
||||
docstring: #docstring,
|
||||
icon: #icon,
|
||||
}
|
||||
@@ -174,10 +182,10 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
|
||||
}
|
||||
}
|
||||
|
||||
impl #crate_name::registry::ChoiceTypeStatic for #name {
|
||||
const WIDGET_HINT: #crate_name::registry::ChoiceWidgetHint = #crate_name::registry::ChoiceWidgetHint::#widget_hint;
|
||||
impl #crate_name::choice_type::ChoiceTypeStatic for #name {
|
||||
const WIDGET_HINT: #crate_name::choice_type::ChoiceWidgetHint = #crate_name::choice_type::ChoiceWidgetHint::#widget_hint;
|
||||
const DESCRIPTION: Option<&'static str> = #enum_description;
|
||||
fn list() -> &'static [&'static [(Self, #crate_name::registry::VariantMetadata)]] {
|
||||
fn list() -> &'static [&'static [(Self, #crate_name::choice_type::VariantMetadata)]] {
|
||||
&[ #(#group)* ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -12,6 +12,7 @@ use syn::{
|
||||
};
|
||||
|
||||
use crate::codegen::generate_node_code;
|
||||
use crate::shader_nodes::ShaderNodeType;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Implementation {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -184,6 +189,8 @@ 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;
|
||||
@@ -191,8 +198,10 @@ impl Parse for NodeFnAttributes {
|
||||
|
||||
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"));
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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"))]
|
||||
@@ -256,6 +283,8 @@ impl Parse for NodeFnAttributes {
|
||||
path,
|
||||
skip_impl,
|
||||
properties_string,
|
||||
cfg,
|
||||
shader_node,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
@@ -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()),
|
||||
@@ -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()),
|
||||
@@ -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()),
|
||||
@@ -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()),
|
||||
@@ -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()),
|
||||
@@ -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()),
|
||||
|
||||
32
node-graph/node-macro/src/shader_nodes.rs
Normal file
32
node-graph/node-macro/src/shader_nodes.rs
Normal file
@@ -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))),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user