From 6fc79b12bdc2f9d5530dd4db0140dbfb97869bb2 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Tue, 18 Mar 2025 20:51:59 +0530 Subject: [PATCH 01/11] Fix min and max macro not enforcing limits when data flows --- node-graph/node-macro/src/codegen.rs | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index af04dd8a0f..0ed6eb4b3e 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -175,6 +175,39 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { + let name = &pat_ident.ident; + let mut tokens = quote! {}; + let is_generic = matches!(ty, syn::Type::Path(p) if p.path.segments.first().map_or(false, |seg| seg.ident.to_string().chars().next().unwrap().is_uppercase())); + if !is_generic { + if let Some(min) = number_min { + tokens = quote! { + #tokens + let #name = if #name < (#min as #ty) { (#min as #ty) } else { #name }; + }; + } + + if let Some(max) = number_max { + tokens = quote! { + #tokens + let #name = if #name > (#max as #ty) { (#max as #ty) } else { #name }; + }; + } + } + tokens + } + ParsedField::Node { .. } => { + quote! {} + } + }); + let all_implementation_types = fields.iter().flat_map(|field| match field { ParsedField::Regular { implementations, .. } => implementations.into_iter().cloned().collect::>(), ParsedField::Node { implementations, .. } => implementations @@ -237,6 +270,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result Self::Output { Box::pin(async move { #(#eval_args)* + #(#min_max_args)* self::#fn_name(__input #(, #field_names)*) #await_keyword }) } From 7a631e893df477558114b72bc72d5529115f083a Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 18 Mar 2025 17:17:24 +0100 Subject: [PATCH 02/11] Use trait based clamping --- node-graph/gcore/src/lib.rs | 1 + node-graph/node-macro/src/codegen.rs | 35 +++++++++++----------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index ff8c75d601..b014d43405 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -9,6 +9,7 @@ use core::future::Future; #[cfg(feature = "log")] extern crate log; pub use crate as graphene_core; +pub use num_traits; #[cfg(feature = "reflections")] pub use ctor; diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 0ed6eb4b3e..b7f3aa3415 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -177,34 +177,25 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let name = &pat_ident.ident; - let mut tokens = quote! {}; - let is_generic = matches!(ty, syn::Type::Path(p) if p.path.segments.first().map_or(false, |seg| seg.ident.to_string().chars().next().unwrap().is_uppercase())); - if !is_generic { - if let Some(min) = number_min { - tokens = quote! { - #tokens - let #name = if #name < (#min as #ty) { (#min as #ty) } else { #name }; - }; - } + let mut tokens = quote!(); + if let Some(min) = number_min { + tokens.extend(quote! { + let #name = #graphene_core::num_traits::clamp_min(#name, #graphene_core::num_traits::FromPrimitive::from_f64(#min).unwrap()); + }); + } - if let Some(max) = number_max { - tokens = quote! { - #tokens - let #name = if #name > (#max as #ty) { (#max as #ty) } else { #name }; - }; - } + if let Some(max) = number_max { + tokens.extend(quote! { + let #name = #graphene_core::num_traits::clamp_max(#name, #graphene_core::num_traits::FromPrimitive::from_f64(#max).unwrap()); + }); } tokens } ParsedField::Node { .. } => { - quote! {} + quote!() } }); @@ -270,7 +261,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result Self::Output { Box::pin(async move { #(#eval_args)* - #(#min_max_args)* + #(#min_max_args)* self::#fn_name(__input #(, #field_names)*) #await_keyword }) } From b4354ed5e80e4adb06100fe5688fa539cb0fb63e Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Tue, 18 Mar 2025 22:16:09 +0530 Subject: [PATCH 03/11] Remove min/max from testing --- node-graph/gcore/src/ops.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 3dc9f6a283..35507cc920 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -338,8 +338,6 @@ fn equals, T>( _: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] - #[min(100.)] - #[max(200.)] other_value: U, ) -> bool { other_value == value @@ -351,8 +349,6 @@ fn not_equals, T>( _: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] - #[min(100.)] - #[max(200.)] other_value: U, ) -> bool { other_value != value From 20305b4b6b741299bd90afdce3d2317aa5ccfe96 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Tue, 18 Mar 2025 22:21:22 +0530 Subject: [PATCH 04/11] cargo fmt --- node-graph/gcore/src/ops.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 35507cc920..21faec9461 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -337,8 +337,7 @@ fn clamp( fn equals, T>( _: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, - #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] - other_value: U, + #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U, ) -> bool { other_value == value } @@ -348,8 +347,7 @@ fn equals, T>( fn not_equals, T>( _: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, - #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] - other_value: U, + #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U, ) -> bool { other_value != value } From 9da8e6470b312c0737859a02dc9988a4ec05468b Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Sat, 22 Mar 2025 10:43:02 +0530 Subject: [PATCH 05/11] Resolve into min, and hard_min --- node-graph/gcore/src/raster/adjustments.rs | 2 +- .../gcore/src/vector/generator_nodes.rs | 4 ++-- node-graph/gcore/src/vector/vector_nodes.rs | 4 ++-- node-graph/gstd/src/image_color_palette.rs | 2 +- node-graph/node-macro/src/codegen.rs | 6 +++--- node-graph/node-macro/src/parsing.rs | 19 ++++++++++++++++++- 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index bd35b3707f..c5763c63ac 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -1399,7 +1399,7 @@ async fn posterize>( )] mut input: T, #[default(4)] - #[min(2.)] + #[hard_min(2.)] levels: u32, ) -> T { input.adjust(|color| { diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index c1ff933787..76ccf56fc7 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -74,7 +74,7 @@ fn regular_polygon( _: impl Ctx, _primary: (), #[default(6)] - #[min(3.)] + #[hard_min(3.)] sides: u32, #[default(50)] radius: f64, ) -> VectorDataTable { @@ -88,7 +88,7 @@ fn star( _: impl Ctx, _primary: (), #[default(5)] - #[min(2.)] + #[hard_min(2.)] sides: u32, #[default(50)] radius: f64, #[default(25)] inner_radius: f64, diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 275f6011e5..2a8398181c 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -625,7 +625,7 @@ async fn poisson_disk_points( _: impl Ctx, vector_data: VectorDataTable, #[default(10.)] - #[min(0.01)] + #[hard_min(0.01)] separation_disk_diameter: f64, seed: SeedValue, ) -> VectorDataTable { @@ -785,7 +785,7 @@ async fn morph( #[range((0., 1.))] #[default(0.5)] time: Fraction, - #[min(0.)] start_index: IntegerCount, + #[hard_min(0.)] start_index: IntegerCount, ) -> VectorDataTable { let time = time.clamp(0., 1.); diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index bd6ce2832d..2b2e986c67 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -6,7 +6,7 @@ async fn image_color_palette( _: impl Ctx, image: ImageFrameTable, #[min(1.)] - #[max(28.)] + #[hard_max(28.)] max_size: u32, ) -> Vec { const GRID: f32 = 3.; diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index b7f3aa3415..6719aef603 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -177,17 +177,17 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let name = &pat_ident.ident; let mut tokens = quote!(); - if let Some(min) = number_min { + if let Some(min) = number_hard_min { tokens.extend(quote! { let #name = #graphene_core::num_traits::clamp_min(#name, #graphene_core::num_traits::FromPrimitive::from_f64(#min).unwrap()); }); } - if let Some(max) = number_max { + if let Some(max) = number_hard_max { tokens.extend(quote! { let #name = #graphene_core::num_traits::clamp_max(#name, #graphene_core::num_traits::FromPrimitive::from_f64(#max).unwrap()); }); diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index c66ae276d5..e6b65b5bf7 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -107,6 +107,8 @@ pub(crate) enum ParsedField { value_source: ParsedValueSource, number_min: Option, number_max: Option, + number_hard_min: Option, + number_hard_max: Option, number_mode_range: Option, implementations: Punctuated, }, @@ -230,7 +232,7 @@ impl Parse for NodeFnAttributes { r#" Unsupported attribute in `node`. Supported attributes are 'category', 'path' and 'name'. - + Example usage: #[node_macro::node(category("Value"), name("Test Node"))] "# @@ -432,6 +434,19 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul }) .transpose()?; + let number_hard_min = extract_attribute(attrs, "hard_min") + .map(|attr| { + attr.parse_args() + .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `hard_min` value for argument '{}': {}", ident, e))) + }) + .transpose()?; + let number_hard_max = extract_attribute(attrs, "hard_max") + .map(|attr| { + attr.parse_args() + .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `hard_max` value for argument '{}': {}", ident, e))) + }) + .transpose()?; + let number_mode_range = extract_attribute(attrs, "range") .map(|attr| { attr.parse_args::().map_err(|e| { @@ -502,6 +517,8 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul exposed, number_min, number_max, + number_hard_min, + number_hard_max, number_mode_range, ty, value_source, From abdc29fea172b23df6b79baa015d91a26bc1da07 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Sat, 22 Mar 2025 10:44:58 +0530 Subject: [PATCH 06/11] cargo fmt --- node-graph/node-macro/src/codegen.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 6719aef603..c898b1a57c 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -177,7 +177,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let name = &pat_ident.ident; let mut tokens = quote!(); From a89263b33b568c0021db3503712dd6751f405b1d Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Sat, 22 Mar 2025 10:55:35 +0530 Subject: [PATCH 07/11] fix traits --- node-graph/gcore/src/vector/generator_nodes.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index a4520983f8..ccbfb36826 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,5 +1,6 @@ use crate::Ctx; use crate::vector::{HandleId, VectorData, VectorDataTable}; +use crate::graphene_core::num_traits::FromPrimitive; use bezier_rs::Subpath; use glam::DVec2; @@ -72,7 +73,7 @@ fn rectangle( } #[node_macro::node(category("Vector: Shape"))] -fn regular_polygon( +fn regular_polygon( _: impl Ctx, _primary: (), #[default(6)] @@ -87,7 +88,7 @@ fn regular_polygon( } #[node_macro::node(category("Vector: Shape"))] -fn star( +fn star( _: impl Ctx, _primary: (), #[default(5)] From affa8760d4952a2a51a519b74724dcd105c571b0 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Sat, 22 Mar 2025 13:48:41 +0530 Subject: [PATCH 08/11] cargo fmt --- node-graph/gcore/src/vector/generator_nodes.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index ccbfb36826..cc45434fd4 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,6 +1,6 @@ use crate::Ctx; -use crate::vector::{HandleId, VectorData, VectorDataTable}; use crate::graphene_core::num_traits::FromPrimitive; +use crate::vector::{HandleId, VectorData, VectorDataTable}; use bezier_rs::Subpath; use glam::DVec2; @@ -73,7 +73,7 @@ fn rectangle( } #[node_macro::node(category("Vector: Shape"))] -fn regular_polygon( +fn regular_polygon( _: impl Ctx, _primary: (), #[default(6)] From 5c57f42f578e89dd0c42f03828413533cb226f16 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Sat, 22 Mar 2025 13:59:55 +0530 Subject: [PATCH 09/11] fix tests --- node-graph/node-macro/src/parsing.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index e6b65b5bf7..e49efc7957 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -735,6 +735,9 @@ mod tests { value_source: ParsedValueSource::None, number_min: None, number_max: None, + + number_hard_min: None, + number_hard_max: None, number_mode_range: None, implementations: Punctuated::new(), }], @@ -800,6 +803,8 @@ mod tests { value_source: ParsedValueSource::None, number_min: None, number_max: None, + number_hard_min: None, + number_hard_max: None, number_mode_range: None, implementations: Punctuated::new(), }, @@ -853,6 +858,9 @@ mod tests { value_source: ParsedValueSource::Default(quote!(50.)), number_min: None, number_max: None, + + number_hard_min: None, + number_hard_max: None, number_mode_range: None, implementations: Punctuated::new(), }], @@ -904,6 +912,9 @@ mod tests { value_source: ParsedValueSource::None, number_min: None, number_max: None, + + number_hard_min: None, + number_hard_max: None, number_mode_range: None, implementations: { let mut p = Punctuated::new(); @@ -967,6 +978,9 @@ mod tests { value_source: ParsedValueSource::None, number_min: Some(parse_quote!(-500.)), number_max: Some(parse_quote!(500.)), + + number_hard_min: None, + number_hard_max: None, number_mode_range: Some(parse_quote!((0., 100.))), implementations: Punctuated::new(), }], @@ -1018,6 +1032,9 @@ mod tests { value_source: ParsedValueSource::None, number_min: None, number_max: None, + + number_hard_min: None, + number_hard_max: None, number_mode_range: None, implementations: Punctuated::new(), }], From 8931efdd5cfed090fce0edbf8fe6d008a9f26b37 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Mon, 24 Mar 2025 13:00:24 +0530 Subject: [PATCH 10/11] rename as soft_x --- node-graph/README.md | 2 +- node-graph/gcore/src/vector/vector_nodes.rs | 6 +-- node-graph/gstd/src/image_color_palette.rs | 2 +- node-graph/node-macro/src/codegen.rs | 14 +++++- node-graph/node-macro/src/parsing.rs | 49 +++++++++------------ 5 files changed, 39 insertions(+), 34 deletions(-) diff --git a/node-graph/README.md b/node-graph/README.md index 0e7e381e1b..6011122f28 100644 --- a/node-graph/README.md +++ b/node-graph/README.md @@ -102,7 +102,7 @@ Instead of manually implementing the `Node` trait with complex generics, one can ```rs #[node_macro::node(category("Raster: Adjustments"))] -fn opacity(_input: (), #[default(424242)] color: Color,#[min(0.1)] opacity_multiplier: f64) -> Color { +fn opacity(_input: (), #[default(424242)] color: Color,#[soft_min(0.1)] opacity_multiplier: f64) -> Color { let opacity_multiplier = opacity_multiplier as f32 / 100.; Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier) } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 11908fd467..24d97ecc2c 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -379,7 +379,7 @@ where async fn round_corners( _: impl Ctx, source: VectorDataTable, - #[min(0.)] + #[hard_min(0.)] #[default(10.)] radius: PixelLength, #[range((0., 1.))] @@ -482,7 +482,7 @@ async fn spatial_merge_by_distance( _: impl Ctx, vector_data: VectorDataTable, #[default(0.1)] - #[min(0.0001)] + #[hard_min(0.0001)] distance: f64, ) -> VectorDataTable { let vector_data_transform = vector_data.transform(); @@ -692,7 +692,7 @@ async fn remove_handles( _: impl Ctx, vector_data: VectorDataTable, #[default(10.)] - #[min(0.)] + #[soft_min(0.)] max_handle_distance: f64, ) -> VectorDataTable { let vector_data_transform = vector_data.transform(); diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index 2b2e986c67..ce47961d74 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -5,7 +5,7 @@ use graphene_core::{Color, Ctx}; async fn image_color_palette( _: impl Ctx, image: ImageFrameTable, - #[min(1.)] + #[hard_min(1.)] #[hard_max(28.)] max_size: u32, ) -> Vec { diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index c898b1a57c..b2553d2640 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -134,14 +134,24 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result = fields .iter() .map(|field| match field { - ParsedField::Regular { number_min: Some(number_min), .. } => quote!(Some(#number_min)), + ParsedField::Regular { number_soft_min, number_hard_min, .. } => match (number_soft_min, number_hard_min) { + (Some(soft_min), None) => quote!(Some(#soft_min)), + (None, Some(hard_min)) => quote!(Some(#hard_min)), + (None, None) => quote!(None), + (Some(soft_min), Some(hard_min)) => quote!(#graphene_core::num_traits::clamp_min(#soft_min, #hard_min)), + }, _ => quote!(None), }) .collect(); let number_max_values: Vec<_> = fields .iter() .map(|field| match field { - ParsedField::Regular { number_max: Some(number_max), .. } => quote!(Some(#number_max)), + ParsedField::Regular { number_soft_max, number_hard_max, .. } => match (number_soft_max, number_hard_max) { + (Some(soft_max), None) => quote!(Some(#soft_max)), + (None, Some(hard_max)) => quote!(Some(#hard_max)), + (None, None) => quote!(None), + (Some(soft_max), Some(hard_max)) => quote!(#graphene_core::num_traits::clamp_max(#soft_max, #hard_max)), + }, _ => quote!(None), }) .collect(); diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index e49efc7957..ee8030d55c 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -105,8 +105,8 @@ pub(crate) enum ParsedField { ty: Type, exposed: bool, value_source: ParsedValueSource, - number_min: Option, - number_max: Option, + number_soft_min: Option, + number_soft_max: Option, number_hard_min: Option, number_hard_max: Option, number_mode_range: Option, @@ -421,16 +421,16 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul _ => ParsedValueSource::None, }; - let number_min = extract_attribute(attrs, "min") + let number_soft_min = extract_attribute(attrs, "soft_min") .map(|attr| { attr.parse_args() - .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `min` value for argument '{}': {}", ident, e))) + .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `soft_min` value for argument '{}': {}", ident, e))) }) .transpose()?; - let number_max = extract_attribute(attrs, "max") + let number_soft_max = extract_attribute(attrs, "soft_max") .map(|attr| { attr.parse_args() - .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `max` value for argument '{}': {}", ident, e))) + .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `soft_max` value for argument '{}': {}", ident, e))) }) .transpose()?; @@ -515,8 +515,8 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul description, widget_override, exposed, - number_min, - number_max, + number_soft_min, + number_soft_max, number_hard_min, number_hard_max, number_mode_range, @@ -733,9 +733,8 @@ mod tests { ty: parse_quote!(f64), exposed: false, value_source: ParsedValueSource::None, - number_min: None, - number_max: None, - + number_soft_min: None, + number_soft_max: None, number_hard_min: None, number_hard_max: None, number_mode_range: None, @@ -801,8 +800,8 @@ mod tests { ty: parse_quote!(DVec2), exposed: false, value_source: ParsedValueSource::None, - number_min: None, - number_max: None, + number_soft_min: None, + number_soft_max: None, number_hard_min: None, number_hard_max: None, number_mode_range: None, @@ -856,9 +855,8 @@ mod tests { ty: parse_quote!(f64), exposed: false, value_source: ParsedValueSource::Default(quote!(50.)), - number_min: None, - number_max: None, - + number_soft_min: None, + number_soft_max: None, number_hard_min: None, number_hard_max: None, number_mode_range: None, @@ -910,9 +908,8 @@ mod tests { ty: parse_quote!(f64), exposed: false, value_source: ParsedValueSource::None, - number_min: None, - number_max: None, - + number_soft_min: None, + number_soft_max: None, number_hard_min: None, number_hard_max: None, number_mode_range: None, @@ -939,8 +936,8 @@ mod tests { a: f64, /// b #[range((0., 100.))] - #[min(-500.)] - #[max(500.)] + #[soft_min(-500.)] + #[soft_max(500.)] b: f64, ) -> f64 { a + b @@ -976,9 +973,8 @@ mod tests { ty: parse_quote!(f64), exposed: false, value_source: ParsedValueSource::None, - number_min: Some(parse_quote!(-500.)), - number_max: Some(parse_quote!(500.)), - + number_soft_min: Some(parse_quote!(-500.)), + number_soft_max: Some(parse_quote!(500.)), number_hard_min: None, number_hard_max: None, number_mode_range: Some(parse_quote!((0., 100.))), @@ -1030,9 +1026,8 @@ mod tests { widget_override: ParsedWidgetOverride::None, exposed: true, value_source: ParsedValueSource::None, - number_min: None, - number_max: None, - + number_soft_min: None, + number_soft_max: None, number_hard_min: None, number_hard_max: None, number_mode_range: None, From 6ace7f68960f5954654971ec6a3f510a63451b42 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Sat, 19 Apr 2025 18:16:11 +0530 Subject: [PATCH 11/11] Add validation code --- node-graph/node-macro/src/codegen.rs | 6 +-- node-graph/node-macro/src/validation.rs | 68 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index b2553d2640..7d98e02637 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -135,10 +135,9 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result match (number_soft_min, number_hard_min) { - (Some(soft_min), None) => quote!(Some(#soft_min)), + (Some(soft_min), _) => quote!(Some(#soft_min)), (None, Some(hard_min)) => quote!(Some(#hard_min)), (None, None) => quote!(None), - (Some(soft_min), Some(hard_min)) => quote!(#graphene_core::num_traits::clamp_min(#soft_min, #hard_min)), }, _ => quote!(None), }) @@ -147,10 +146,9 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result match (number_soft_max, number_hard_max) { - (Some(soft_max), None) => quote!(Some(#soft_max)), + (Some(soft_max), _) => quote!(Some(#soft_max)), (None, Some(hard_max)) => quote!(Some(#hard_max)), (None, None) => quote!(None), - (Some(soft_max), Some(hard_max)) => quote!(#graphene_core::num_traits::clamp_max(#soft_max, #hard_max)), }, _ => quote!(None), }) diff --git a/node-graph/node-macro/src/validation.rs b/node-graph/node-macro/src/validation.rs index e6eece2128..c475fff6db 100644 --- a/node-graph/node-macro/src/validation.rs +++ b/node-graph/node-macro/src/validation.rs @@ -9,6 +9,7 @@ pub fn validate_node_fn(parsed: &ParsedNodeFn) -> syn::Result<()> { // Add more validators here as needed validate_implementations_for_generics, validate_primary_input_expose, + validate_min_max, ]; for validator in validators { @@ -18,6 +19,73 @@ pub fn validate_node_fn(parsed: &ParsedNodeFn) -> syn::Result<()> { Ok(()) } +fn validate_min_max(parsed: &ParsedNodeFn) { + for field in &parsed.fields { + match field { + ParsedField::Regular { + number_hard_max, + number_hard_min, + number_soft_max, + number_soft_min, + pat_ident, + .. + } => { + match (number_soft_min, number_hard_min) { + (Some(soft_min), Some(hard_min)) => { + let soft_min_value: f64 = soft_min.base10_parse().unwrap_or_default(); + let hard_min_value: f64 = hard_min.base10_parse().unwrap_or_default(); + if soft_min_value == hard_min_value { + emit_error!( + pat_ident.span(), + "Unnecessary #[soft_min] attribute on `{}` as #[hard_min] equals to it.", + pat_ident.ident; + help = "You can safely remove the #[soft_min] attribute from this field."; + note = "The #[hard_min] also limits the range to same without #[soft_min]", + ); + } else if soft_min_value < hard_min_value { + emit_error!( + pat_ident.span(), + "The #[soft_min] attribute exists on `{}` and is lower than #[hard_min].", + pat_ident.ident; + help = "You probably meant to reverse the two macros"; + note = "Allowing the possible range in slider to be more than #[hard_min] doesn't make sense", + ); + } + } + _ => (), + } + + match (number_soft_max, number_hard_max) { + (Some(soft_max), Some(hard_max)) => { + let soft_max_value: f64 = soft_max.base10_parse().unwrap_or_default(); + let hard_max_value: f64 = hard_max.base10_parse().unwrap_or_default(); + if soft_max_value == hard_max_value { + emit_error!( + pat_ident.span(), + "Unnecessary #[soft_max] attribute on `{}` as #[hard_max] equals to it.", + pat_ident.ident; + help = "You can safely remove the #[soft_max] attribute from this field."; + note = "The #[hard_max] also limits the range to same without #[soft_max]", + ); + } else if soft_max_value < hard_max_value { + emit_error!( + pat_ident.span(), + "The #[soft_max] attribute exists on `{}` and is greater than #[hard_max].", + pat_ident.ident; + help = "You probably meant to reverse the two macros"; + note = "Allowing the possible range in slider to be more than #[hard_max] doesn't make sense", + ); + } + } + _ => (), + } + } + + _ => (), + } + } +} + fn validate_primary_input_expose(parsed: &ParsedNodeFn) { if let Some(ParsedField::Regular { exposed: true, pat_ident, .. }) = parsed.fields.first() { emit_error!(