Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ use graph_craft::concrete;
use graph_craft::document::value::*;
use graph_craft::document::*;
use graphene_std::brush::brush_cache::BrushCache;
use graphene_std::extract_xy::XY;
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType};
use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
#[allow(unused_imports)]
Expand Down Expand Up @@ -1201,104 +1200,15 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
category: "Raster: Channels",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::node(NodeId(0), 0),
NodeInput::node(NodeId(1), 0),
NodeInput::node(NodeId(2), 0),
NodeInput::node(NodeId(3), 0),
],
nodes: [
DocumentNode {
inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::split_channels::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
call_argument: generic!(T),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Image", "TODO").into()],
output_names: vec!["".to_string(), "Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 4)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 6)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
network_metadata: None,
..Default::default()
},
},
Expand All @@ -1309,62 +1219,16 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
identifier: "Split Vec2",
category: "Math: Vector",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::value(TaggedValue::None, false), NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::import(concrete!(DVec2), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::import(concrete!(DVec2), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),

..Default::default()
}),
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::split_vec_2::IDENTIFIER),
Comment thread
JustJ01 marked this conversation as resolved.
Outdated
inputs: vec![NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), true)],
call_argument: generic!(T),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Vec2", "TODO").into()],
output_names: vec!["".to_string(), "X".to_string(), "Y".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
output_names: vec!["X".to_string(), "Y".to_string()],
network_metadata: None,
..Default::default()
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
description,
properties,
context_features,
output_fields: _,
} = metadata;

let Some(implementations) = &node_registry.get(id) else { continue };
Expand Down
14 changes: 14 additions & 0 deletions node-graph/libraries/core-types/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct NodeMetadata {
pub description: &'static str,
pub properties: Option<&'static str>,
pub context_features: Vec<ContextFeature>,
pub output_fields: &'static [StructField],
}

// Translation struct between macro and definition
Expand All @@ -36,6 +37,19 @@ pub struct FieldMetadata {
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() -> &'static [StructField] {
&[]
}
}

#[derive(Clone, Debug)]
pub enum RegistryWidgetOverride {
None,
Expand Down
16 changes: 12 additions & 4 deletions node-graph/node-macro/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(&[] as &[#core_types::registry::StructField]),
true => quote!(<#output_type as #core_types::registry::Destruct>::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()))?;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -548,21 +554,23 @@ fn generate_node_input_references(
impl <#(#used),*> #core_types::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> {
const INDEX: usize = #input_index;
fn identifier() -> #core_types::ProtoNodeIdentifier {
#inputs_module_name::IDENTIFIER.clone()
protonode_identifier()
}
type Result = #ty;
}
})
});
}
}

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

/// The `ProtoNodeIdentifier` of this node without any generics attached to it
pub const IDENTIFIER: #core_types::ProtoNodeIdentifier = #identifier();

pub fn protonode_identifier() -> #core_types::ProtoNodeIdentifier {
IDENTIFIER
}
#(#generated_input_accessor)*
}
}
Expand Down
115 changes: 115 additions & 0 deletions node-graph/node-macro/src/destruct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use convert_case::{Case, Casing};
Comment thread
Keavon marked this conversation as resolved.
use proc_macro_crate::FoundCrate;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Error, LitStr, Meta, Type, spanned::Spanned};

pub fn derive(input: DeriveInput) -> syn::Result<TokenStream2> {
let struct_name = input.ident;
let generics = input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

let Data::Struct(data_struct) = input.data else {
return Err(Error::new(Span::call_site(), "Deriving `Destruct` is currently only supported for structs"));
};

let core_types = match proc_macro_crate::crate_name("core-types")
.map_err(|e| Error::new(Span::call_site(), format!("Failed to find location of core_types. Make sure it is imported as a dependency: {e}")))?
{
FoundCrate::Itself => quote!(crate),
FoundCrate::Name(name) => {
let ident = syn::Ident::new(&name, Span::call_site());
quote!(#ident)
}
};

let mut node_implementations = Vec::with_capacity(data_struct.fields.len());
let mut output_fields = 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(), "Destruct cannot be used on tuple structs"));
};

let ty = field.ty;
let output_name = parse_output_name(&field.attrs)?.unwrap_or_else(|| field_name.to_string().to_case(Case::Title));
let output_name_lit = LitStr::new(&output_name, field_name.span());

let fn_name = format_ident!("extract_{}_{}", struct_name.to_string().to_case(Case::Snake), field_name);
let node_struct_name = format_ident!("{}Node", fn_name.to_string().to_case(Case::Pascal));

node_implementations.push(generate_extractor_node(&core_types, &fn_name, &struct_name, &field_name, &ty, &output_name_lit));
output_fields.push(quote! {
#core_types::registry::StructField {
name: #output_name_lit,
node_path: concat!(std::module_path!(), "::", stringify!(#node_struct_name)),
ty: #core_types::concrete!(#ty),
}
});
}

Ok(quote! {
#(#node_implementations)*

impl #impl_generics #core_types::registry::Destruct for #struct_name #ty_generics #where_clause {
fn fields() -> &'static [#core_types::registry::StructField] {
static FIELDS: std::sync::OnceLock<Vec<#core_types::registry::StructField>> = std::sync::OnceLock::new();
FIELDS.get_or_init(|| vec![
#(#output_fields,)*
]).as_slice()
Comment on lines +68 to +71
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use heap memory + syncronization here instead of constructing a slice in place?

}
}
})
}

fn generate_extractor_node(core_types: &TokenStream2, fn_name: &syn::Ident, struct_name: &syn::Ident, field_name: &syn::Ident, ty: &Type, output_name: &LitStr) -> TokenStream2 {
quote! {
#[node_macro::node(category(""), name(#output_name))]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we add the name() annotation here?

fn #fn_name(_: impl #core_types::Ctx, data: #struct_name) -> #ty {
data.#field_name
}
}
}

fn parse_output_name(attrs: &[syn::Attribute]) -> syn::Result<Option<String>> {
let mut output_name = None;

for attr in attrs {
if !attr.path().is_ident("output") {
continue;
}

let mut this_output_name = None;
match &attr.meta {
Meta::Path(_) => {
return Err(Error::new_spanned(attr, "Expected output metadata like #[output(name = \"Result\")]"));
}
Meta::NameValue(_) => {
return Err(Error::new_spanned(attr, "Expected output metadata like #[output(name = \"Result\")]"));
}
Meta::List(_) => {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
if this_output_name.is_some() {
return Err(meta.error("Multiple output names provided for one field"));
}
let value = meta.value()?;
let lit: LitStr = value.parse()?;
this_output_name = Some(lit.value());
Ok(())
} else {
Err(meta.error("Unsupported output metadata. Supported syntax is #[output(name = \"...\")]"))
}
})?;
}
}

let this_output_name = this_output_name.ok_or_else(|| Error::new_spanned(attr, "Missing output name. Use #[output(name = \"...\")]"))?;
if output_name.is_some() {
return Err(Error::new_spanned(attr, "Multiple #[output(...)] attributes are not allowed on one field"));
}
output_name = Some(this_output_name);
}

Ok(output_name)
}
Comment on lines +90 to +131
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function only used for overriding the field name? I don't think this code is currently used anywhere right? If it is indeed not used, it should be removed to reduce the code complexity

Loading
Loading