Skip to content

Commit d0bf7da

Browse files
committed
Avoid needing to add trait bounds to node definitions
1 parent c764e55 commit d0bf7da

File tree

5 files changed

+93
-16
lines changed

5 files changed

+93
-16
lines changed

node-graph/gcore/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod context;
2020
pub mod generic;
2121
pub mod instances;
2222
pub mod logic;
23+
pub mod misc;
2324
pub mod ops;
2425
pub mod structural;
2526
#[cfg(feature = "std")]

node-graph/gcore/src/misc.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/// A trait for types that can be clamped within a min/max range defined by f64.
2+
pub trait Clampable: Sized {
3+
/// Clamps the value to be no less than `min`.
4+
fn clamp_hard_min(self, min: f64) -> Self;
5+
/// Clamps the value to be no more than `max`.
6+
fn clamp_hard_max(self, max: f64) -> Self;
7+
}
8+
9+
// Implement for common numeric types
10+
macro_rules! impl_clampable_float {
11+
($($ty:ty),*) => {
12+
$(
13+
impl Clampable for $ty {
14+
#[inline(always)]
15+
fn clamp_hard_min(self, min: f64) -> Self {
16+
self.max(min as $ty)
17+
}
18+
#[inline(always)]
19+
fn clamp_hard_max(self, max: f64) -> Self {
20+
self.min(max as $ty)
21+
}
22+
}
23+
)*
24+
};
25+
}
26+
impl_clampable_float!(f32, f64);
27+
28+
macro_rules! impl_clampable_int {
29+
($($ty:ty),*) => {
30+
$(
31+
impl Clampable for $ty {
32+
#[inline(always)]
33+
fn clamp_hard_min(self, min: f64) -> Self {
34+
// Using try_from to handle potential range issues safely, though min should ideally be valid.
35+
// Consider using a different approach if f64 precision vs integer range is a concern.
36+
<$ty>::try_from(min.ceil() as i64).ok().map_or(self, |min_val| self.max(min_val))
37+
}
38+
#[inline(always)]
39+
fn clamp_hard_max(self, max: f64) -> Self {
40+
<$ty>::try_from(max.floor() as i64).ok().map_or(self, |max_val| self.min(max_val))
41+
}
42+
}
43+
)*
44+
};
45+
}
46+
// Add relevant integer types (adjust as needed)
47+
impl_clampable_int!(u32, u64, i32, i64);
48+
49+
// Implement for DVec2 (component-wise clamping)
50+
use glam::DVec2;
51+
impl Clampable for DVec2 {
52+
#[inline(always)]
53+
fn clamp_hard_min(self, min: f64) -> Self {
54+
self.max(DVec2::splat(min))
55+
}
56+
#[inline(always)]
57+
fn clamp_hard_max(self, max: f64) -> Self {
58+
self.min(DVec2::splat(max))
59+
}
60+
}

node-graph/gcore/src/vector/generator_nodes.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use super::misc::{ArcType, AsU64, GridType};
22
use super::{PointId, SegmentId, StrokeId};
33
use crate::Ctx;
4-
use crate::graphene_core::num_traits::FromPrimitive;
54
use crate::registry::types::Angle;
65
use crate::vector::{HandleId, VectorData, VectorDataTable};
76
use bezier_rs::Subpath;
@@ -98,7 +97,7 @@ fn rectangle<T: CornerRadius>(
9897
}
9998

10099
#[node_macro::node(category("Vector: Shape"))]
101-
fn regular_polygon<T: AsU64 + std::cmp::PartialOrd + FromPrimitive>(
100+
fn regular_polygon<T: AsU64>(
102101
_: impl Ctx,
103102
_primary: (),
104103
#[default(6)]
@@ -113,7 +112,7 @@ fn regular_polygon<T: AsU64 + std::cmp::PartialOrd + FromPrimitive>(
113112
}
114113

115114
#[node_macro::node(category("Vector: Shape"))]
116-
fn star<T: AsU64 + std::cmp::PartialOrd + FromPrimitive>(
115+
fn star<T: AsU64>(
117116
_: impl Ctx,
118117
_primary: (),
119118
#[default(5)]
@@ -150,7 +149,7 @@ impl GridSpacing for DVec2 {
150149
}
151150

152151
#[node_macro::node(category("Vector: Shape"), properties("grid_properties"))]
153-
fn grid<T: GridSpacing + std::cmp::PartialOrd + FromPrimitive>(
152+
fn grid<T: GridSpacing>(
154153
_: impl Ctx,
155154
_primary: (),
156155
grid_type: GridType,

node-graph/node-macro/src/codegen.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::parsing::*;
22
use convert_case::{Case, Casing};
33
use proc_macro_crate::FoundCrate;
44
use proc_macro2::TokenStream as TokenStream2;
5-
use quote::{format_ident, quote};
5+
use quote::{format_ident, quote, quote_spanned};
66
use std::sync::atomic::AtomicU64;
77
use syn::punctuated::Punctuated;
88
use syn::spanned::Spanned;
@@ -193,14 +193,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
193193
let name = &pat_ident.ident;
194194
let mut tokens = quote!();
195195
if let Some(min) = number_hard_min {
196-
tokens.extend(quote! {
197-
let #name = #graphene_core::num_traits::clamp_min(#name, #graphene_core::num_traits::FromPrimitive::from_f64(#min).unwrap());
196+
tokens.extend(quote_spanned! {min.span()=>
197+
let #name = #graphene_core::misc::Clampable::clamp_hard_min(#name, #min);
198198
});
199199
}
200200

201201
if let Some(max) = number_hard_max {
202-
tokens.extend(quote! {
203-
let #name = #graphene_core::num_traits::clamp_max(#name, #graphene_core::num_traits::FromPrimitive::from_f64(#max).unwrap());
202+
tokens.extend(quote_spanned! {max.span()=>
203+
let #name = #graphene_core::misc::Clampable::clamp_hard_max(#name, #max);
204204
});
205205
}
206206
tokens
@@ -221,13 +221,27 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
221221

222222
let input_type = &parsed.input.ty;
223223
let mut clauses = Vec::new();
224+
let mut clampable_clauses = Vec::new();
225+
224226
for (field, name) in fields.iter().zip(struct_generics.iter()) {
225227
clauses.push(match (field, *is_async) {
226-
(ParsedField::Regular { ty, .. }, _) => {
228+
(
229+
ParsedField::Regular {
230+
ty, number_hard_min, number_hard_max, ..
231+
},
232+
_,
233+
) => {
227234
let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all");
228235
let id = future_idents.len();
229236
let fut_ident = format_ident!("F{}", id);
230237
future_idents.push(fut_ident.clone());
238+
239+
// Add Clampable bound if this field uses hard_min or hard_max
240+
if number_hard_min.is_some() || number_hard_max.is_some() {
241+
// The bound applies to the Output type of the future, which is #ty
242+
clampable_clauses.push(quote!(#ty: #graphene_core::misc::Clampable));
243+
}
244+
231245
quote!(
232246
#fut_ident: core::future::Future<Output = #ty> + #graphene_core::WasmNotSend + 'n,
233247
for<'all> #all_lifetime_ty: #graphene_core::WasmNotSend,
@@ -255,6 +269,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
255269
let mut struct_where_clause = where_clause.clone();
256270
let extra_where: Punctuated<WherePredicate, Comma> = parse_quote!(
257271
#(#clauses,)*
272+
#(#clampable_clauses,)*
258273
#output_type: 'n,
259274
);
260275
struct_where_clause.predicates.extend(extra_where);
@@ -271,6 +286,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
271286
#[inline]
272287
fn eval(&'n self, __input: #input_type) -> Self::Output {
273288
Box::pin(async move {
289+
use #graphene_core::misc::Clampable;
290+
274291
#(#eval_args)*
275292
#(#min_max_args)*
276293
self::#fn_name(__input #(, #field_names)*) #await_keyword

proc-macros/src/widget_builder.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ fn easier_string_assignment(field_ty: &Type, field_ident: &Ident) -> (TokenStrea
2121
// Based on https://stackoverflow.com/questions/66906261/rust-proc-macro-derive-how-do-i-check-if-a-field-is-of-a-primitive-type-like-b
2222
if last_segment.ident == Ident::new("String", last_segment.ident.span()) {
2323
return (
24-
quote::quote_spanned!(type_path.span() => impl Into<String>),
25-
quote::quote_spanned!(field_ident.span() => #field_ident.into()),
24+
quote::quote_spanned!(type_path.span()=> impl Into<String>),
25+
quote::quote_spanned!(field_ident.span()=> #field_ident.into()),
2626
);
2727
}
2828
}
2929
}
30-
(quote::quote_spanned!(field_ty.span() => #field_ty), quote::quote_spanned!(field_ident.span() => #field_ident))
30+
(quote::quote_spanned!(field_ty.span()=> #field_ty), quote::quote_spanned!(field_ident.span()=> #field_ident))
3131
}
3232

3333
/// Extract the identifier of the field (which should always be present)
@@ -54,8 +54,8 @@ fn find_type_and_assignment(field: &Field) -> syn::Result<(TokenStream2, TokenSt
5454
if let Some(first_generic) = generic_args.args.first() {
5555
if last_segment.ident == Ident::new("WidgetCallback", last_segment.ident.span()) {
5656
// Assign builder pattern to assign the closure directly
57-
function_input_ty = quote::quote_spanned!(field_ty.span() => impl Fn(&#first_generic) -> crate::messages::message::Message + 'static + Send + Sync);
58-
assignment = quote::quote_spanned!(field_ident.span() => crate::messages::layout::utility_types::layout_widget::WidgetCallback::new(#field_ident));
57+
function_input_ty = quote::quote_spanned!(field_ty.span()=> impl Fn(&#first_generic) -> crate::messages::message::Message + 'static + Send + Sync);
58+
assignment = quote::quote_spanned!(field_ident.span()=> crate::messages::layout::utility_types::layout_widget::WidgetCallback::new(#field_ident));
5959
}
6060
}
6161
}
@@ -78,7 +78,7 @@ fn construct_builder(field: &Field) -> syn::Result<TokenStream2> {
7878
let (function_input_ty, assignment) = find_type_and_assignment(field)?;
7979

8080
// Create builder function
81-
Ok(quote::quote_spanned!(field.span() =>
81+
Ok(quote::quote_spanned!(field.span()=>
8282
#[doc = #doc_comment]
8383
pub fn #field_ident(mut self, #field_ident: #function_input_ty) -> Self{
8484
self.#field_ident = #assignment;

0 commit comments

Comments
 (0)