From 3a7af1c3fbe55f0223604679f9e4e169060ea6fb Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sun, 12 Jan 2025 14:41:29 +0100 Subject: [PATCH 01/34] Implement experimental Context struct and traits --- libraries/dyn-any/src/lib.rs | 19 ++++ node-graph/gcore/src/context.rs | 151 ++++++++++++++++++++++++++++++++ node-graph/gcore/src/lib.rs | 3 +- 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 node-graph/gcore/src/context.rs diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index 41e0c60506..25e1b613fe 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -58,6 +58,12 @@ pub trait DynAny<'a>: 'a { fn type_id(&self) -> TypeId; #[cfg(feature = "log-bad-types")] fn type_name(&self) -> &'static str; + fn reborrow_box<'short>(self: Box) -> Box + 'short> + where + 'a: 'short; + fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + 'short) + where + 'a: 'short; } impl<'a, T: StaticType + 'a> DynAny<'a> for T { @@ -68,6 +74,19 @@ impl<'a, T: StaticType + 'a> DynAny<'a> for T { fn type_name(&self) -> &'static str { core::any::type_name::() } + fn reborrow_box<'short>(self: Box) -> Box + 'short> + where + 'a: 'short, + { + self + } + + fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + 'short) + where + 'a: 'short, + { + self + } } pub fn downcast_ref<'a, V: StaticType + 'a>(i: &'a dyn DynAny<'a>) -> Option<&'a V> { if i.type_id() == core::any::TypeId::of::<::Static>() { diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs new file mode 100644 index 0000000000..8af464a3ae --- /dev/null +++ b/node-graph/gcore/src/context.rs @@ -0,0 +1,151 @@ +use core::borrow::Borrow; + +use crate::transform::Footprint; +use dyn_any::DynAny; + +pub trait ExtractFootprint { + fn footprint(&self) -> Option<&Footprint>; +} + +pub trait ExtractTime { + fn time(&self) -> Option; +} + +pub trait ExtractIndex { + fn index(&self) -> Option; +} + +pub trait ExtractVarArgs { + // Call this lifetime 'b so it is less likely to coflict when auto generating the function signature for implementation + fn vararg<'b>(&'b self, index: usize) -> Result, VarArgsResult> + where + Self: 'b; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VarArgsResult { + IndexOutOfBounds, + NoVarArgs, +} + +impl ExtractFootprint for Option<&T> { + fn footprint(&self) -> Option<&Footprint> { + self.and_then(|x| x.footprint()) + } +} +impl ExtractTime for Option<&T> { + fn time(&self) -> Option { + self.and_then(|x| x.time()) + } +} +impl ExtractIndex for Option<&T> { + fn index(&self) -> Option { + self.and_then(|x| x.index()) + } +} +impl ExtractVarArgs for Option<&T> { + fn vararg<'b>(&'b self, index: usize) -> Result, VarArgsResult> + where + Self: 'b, + { + let Some(inner) = self else { return Err(VarArgsResult::NoVarArgs) }; + inner.vararg(index) + } +} + +impl ExtractFootprint for ContextImpl<'_> { + fn footprint(&self) -> Option<&Footprint> { + self.footprint + } +} +impl ExtractTime for ContextImpl<'_> { + fn time(&self) -> Option { + self.time + } +} +impl ExtractIndex for ContextImpl<'_> { + fn index(&self) -> Option { + self.index + } +} +impl ExtractVarArgs for ContextImpl<'_> { + fn vararg<'b>(&'b self, index: usize) -> Result, VarArgsResult> + where + Self: 'b, + { + let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; + inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds) + } +} + +pub type Context<'a> = Option<&'a ContextImpl<'a>>; +type DynRef<'a> = &'a (dyn DynAny<'a> + 'a); + +#[derive(Default, Clone, Copy)] +pub struct ContextImpl<'a> { + footprint: Option<&'a crate::transform::Footprint>, + varargs: Option<&'a [&'a (dyn dyn_any::DynAny<'a> + 'a)]>, + // This could be converted into a single enum to save extra bytes + index: Option, + time: Option, +} + +impl<'a> ContextImpl<'a> { + pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f> + where + 'a: 'f, + { + ContextImpl { + footprint: Some(new_footprint), + varargs: varargs.map(|x| x.borrow()), + ..*self + } + } + #[cfg(feature = "alloc")] + pub fn reborrow_var_args_to_vec<'short>(&self) -> Option]>> + where + 'a: 'short, + { + self.varargs.map(|x| shorten_lifetime_to_vec(x).into()) + } + pub fn reborrow_var_args_to_buffer<'short, const N: usize>(&self, buffer: &'short mut [DynRef<'short>; N]) -> Option<&'short [DynRef<'short>]> + where + 'a: 'short, + { + self.varargs.map(|x| shorten_lifetime_to_buffer(x, buffer)) + } +} + +fn shorten_lifetime_to_vec<'c, 'b: 'c>(input: &'b [&'b (dyn DynAny<'b> + 'b)]) -> Vec<&'c (dyn DynAny<'c> + 'c)> { + input.iter().map(|&x| x.reborrow_ref()).collect() +} +fn shorten_lifetime_to_buffer<'c, 'b: 'c, const N: usize>(input: &'b [&'b (dyn DynAny<'b> + 'b)], buffer: &'c mut [&'c (dyn DynAny<'c> + 'c); N]) -> &'c [&'c (dyn DynAny<'c> + 'c)] { + let iter = input.iter().map(|&x| x.reborrow_ref()).zip(buffer.iter_mut()); + if input.len() > N { + unreachable!("Insufficient buffer size for varargs"); + } + for (data, buffer_slot) in iter { + *buffer_slot = data.reborrow_ref(); + } + &buffer[..input.len()] +} + +#[test] +fn shorten_lifetime_compile_test() { + let context: ContextImpl<'static> = const { + ContextImpl { + footprint: None, + varargs: None, + index: None, + time: None, + } + }; + let footprint = Footprint::default(); + let local_varargs = context.reborrow_var_args_to_vec(); + let out = context.with_footprint(&footprint, local_varargs.as_ref()); + assert!(out.footprint().is_some()); + let mut buffer: [_; 0] = []; + let local_varargs_buf = context.reborrow_var_args_to_buffer(&mut buffer); + let out = context.with_footprint(&footprint, local_varargs_buf.as_ref()); + assert!(out.footprint().is_some()); +} diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index be6487ed43..16ff18f867 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -14,6 +14,7 @@ pub use crate as graphene_core; pub use ctor; pub mod consts; +pub mod context; pub mod generic; pub mod logic; pub mod ops; @@ -55,7 +56,7 @@ pub use types::Cow; // pub trait Node: for<'n> NodeIO<'n> { /// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct. /// See `node-graph/README.md` for information on how to define a new node. -pub trait Node<'i, Input: 'i>: 'i { +pub trait Node<'i, Input>: 'i { type Output: 'i; /// Evaluates the node with the single specified input. fn eval(&'i self, input: Input) -> Self::Output; From b1be69b957c981e671fb8a2702218613841ddb05 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sun, 12 Jan 2025 15:47:03 +0100 Subject: [PATCH 02/34] Add Ctx super trait --- libraries/dyn-any/src/lib.rs | 8 ++++--- node-graph/gcore/src/context.rs | 27 +++++++++++++--------- node-graph/gcore/src/lib.rs | 1 + node-graph/gcore/src/ops.rs | 3 ++- node-graph/node-macro/src/parsing.rs | 34 ++++++++++++++++++++++++---- 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index 25e1b613fe..2da3cd93b0 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -61,9 +61,10 @@ pub trait DynAny<'a>: 'a { fn reborrow_box<'short>(self: Box) -> Box + 'short> where 'a: 'short; - fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + 'short) + fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + Send + Sync + 'short) where - 'a: 'short; + 'a: 'short, + Self: Send + Sync; } impl<'a, T: StaticType + 'a> DynAny<'a> for T { @@ -81,9 +82,10 @@ impl<'a, T: StaticType + 'a> DynAny<'a> for T { self } - fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + 'short) + fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + Send + Sync + 'short) where 'a: 'short, + Self: Send + Sync, { self } diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 8af464a3ae..efd08b94a2 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -3,21 +3,23 @@ use core::borrow::Borrow; use crate::transform::Footprint; use dyn_any::DynAny; -pub trait ExtractFootprint { +pub trait Ctx {} + +pub trait ExtractFootprint: Ctx { fn footprint(&self) -> Option<&Footprint>; } -pub trait ExtractTime { +pub trait ExtractTime: Ctx { fn time(&self) -> Option; } -pub trait ExtractIndex { +pub trait ExtractIndex: Ctx { fn index(&self) -> Option; } -pub trait ExtractVarArgs { +pub trait ExtractVarArgs: Ctx { // Call this lifetime 'b so it is less likely to coflict when auto generating the function signature for implementation - fn vararg<'b>(&'b self, index: usize) -> Result, VarArgsResult> + fn vararg<'b>(&'b self, index: usize) -> Result + Send + Sync, VarArgsResult> where Self: 'b; } @@ -27,6 +29,7 @@ pub enum VarArgsResult { IndexOutOfBounds, NoVarArgs, } +impl Ctx for Option<&T> {} impl ExtractFootprint for Option<&T> { fn footprint(&self) -> Option<&Footprint> { @@ -53,6 +56,8 @@ impl ExtractVarArgs for Option<&T> { } } +impl Ctx for ContextImpl<'_> {} + impl ExtractFootprint for ContextImpl<'_> { fn footprint(&self) -> Option<&Footprint> { self.footprint @@ -69,7 +74,7 @@ impl ExtractIndex for ContextImpl<'_> { } } impl ExtractVarArgs for ContextImpl<'_> { - fn vararg<'b>(&'b self, index: usize) -> Result, VarArgsResult> + fn vararg<'b>(&'b self, index: usize) -> Result + Send + Sync, VarArgsResult> where Self: 'b, { @@ -79,12 +84,12 @@ impl ExtractVarArgs for ContextImpl<'_> { } pub type Context<'a> = Option<&'a ContextImpl<'a>>; -type DynRef<'a> = &'a (dyn DynAny<'a> + 'a); +type DynRef<'a> = &'a (dyn DynAny<'a> + 'a + Send + Sync); -#[derive(Default, Clone, Copy)] +#[derive(Default, Clone, Copy, dyn_any::DynAny)] pub struct ContextImpl<'a> { footprint: Option<&'a crate::transform::Footprint>, - varargs: Option<&'a [&'a (dyn dyn_any::DynAny<'a> + 'a)]>, + varargs: Option<&'a [DynRef<'a>]>, // This could be converted into a single enum to save extra bytes index: Option, time: Option, @@ -116,10 +121,10 @@ impl<'a> ContextImpl<'a> { } } -fn shorten_lifetime_to_vec<'c, 'b: 'c>(input: &'b [&'b (dyn DynAny<'b> + 'b)]) -> Vec<&'c (dyn DynAny<'c> + 'c)> { +fn shorten_lifetime_to_vec<'c, 'b: 'c>(input: &'b [DynRef<'b>]) -> Vec> { input.iter().map(|&x| x.reborrow_ref()).collect() } -fn shorten_lifetime_to_buffer<'c, 'b: 'c, const N: usize>(input: &'b [&'b (dyn DynAny<'b> + 'b)], buffer: &'c mut [&'c (dyn DynAny<'c> + 'c); N]) -> &'c [&'c (dyn DynAny<'c> + 'c)] { +fn shorten_lifetime_to_buffer<'c, 'b: 'c, const N: usize>(input: &'b [DynRef<'b>], buffer: &'c mut [DynRef<'c>; N]) -> &'c [DynRef<'c>] { let iter = input.iter().map(|&x| x.reborrow_ref()).zip(buffer.iter_mut()); if input.len() > N { unreachable!("Insufficient buffer size for varargs"); diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 16ff18f867..b69703194d 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -48,6 +48,7 @@ pub mod application_io; #[cfg(feature = "reflections")] pub mod registry; +pub use context::*; use core::any::TypeId; pub use memo::MemoHash; pub use raster::Color; diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 241bf4bc0d..ca8a3296db 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -2,6 +2,7 @@ use crate::raster::BlendMode; use crate::raster::ImageFrame; use crate::registry::types::Percentage; use crate::vector::style::GradientStops; +use crate::Ctx; use crate::{Color, Node}; use math_parser::ast; @@ -39,7 +40,7 @@ impl ValueProvider for MathNodeContext { /// Calculates a mathematical expression with input values "A" and "B" #[node_macro::node(category("Math"))] fn math( - _: (), + _: impl Ctx, /// The value of "A" when calculating the expression #[implementations(f64, f32)] operand_a: U, diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index d263c5ba6c..b3d0d0da73 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -4,8 +4,11 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, ToTokens}; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::token::{Comma, RArrow}; -use syn::{AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, WhereClause}; +use syn::{ + parse_quote, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, WhereClause, +}; use crate::codegen::generate_node_code; @@ -482,10 +485,12 @@ fn extract_attribute<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attri // Modify the new_node_fn function to use the code generation pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 { let parse_result = parse_node_fn(attr, item.clone()); - let Ok(parsed_node) = parse_result else { + let Ok(mut parsed_node) = parse_result else { let e = parse_result.unwrap_err(); return Error::new(e.span(), format!("Failed to parse node function: {e}")).to_compile_error(); }; + + parsed_node.replace_impl_trait_in_input(); if let Err(e) = crate::validation::validate_node_fn(&parsed_node) { return Error::new(e.span(), format!("Validation Error:\n{e}")).to_compile_error(); } @@ -498,6 +503,27 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 { } } +impl ParsedNodeFn { + fn replace_impl_trait_in_input(&mut self) { + if let Type::ImplTrait(impl_trait) = self.input.ty.clone() { + let ident = Ident::new("_Input", impl_trait.span()); + let bounds = impl_trait.bounds; + self.fn_generics.push(GenericParam::Type(TypeParam { + attrs: Default::default(), + ident: ident.clone(), + colon_token: Some(Default::default()), + bounds, + eq_token: None, + default: None, + })); + self.input.ty = parse_quote!(#ident); + if self.input.implementations.is_empty() { + self.input.implementations.push(parse_quote!(gcore::Context)); + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -703,7 +729,7 @@ mod tests { let attr = quote!(category("Vector: Shape")); let input = quote!( /// Test - fn circle(_: (), #[default(50.)] radius: f64) -> VectorData { + fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> VectorData { // Implementation details... } ); @@ -723,7 +749,7 @@ mod tests { where_clause: None, input: Input { pat_ident: pat_ident("_"), - ty: parse_quote!(()), + ty: parse_quote!(impl Ctx), implementations: Punctuated::new(), }, output_type: parse_quote!(VectorData), From 247174f8ca1dc38647a89c2cedcc923114162cd5 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 13 Jan 2025 19:11:50 +0100 Subject: [PATCH 03/34] Checkpoint --- node-graph/gcore/src/context.rs | 16 +- node-graph/gcore/src/graphic_element.rs | 388 +++---- node-graph/gcore/src/lib.rs | 14 +- node-graph/gcore/src/logic.rs | 31 +- node-graph/gcore/src/ops.rs | 987 +++++++++--------- node-graph/gcore/src/raster.rs | 46 +- node-graph/gcore/src/raster/adjustments.rs | 415 +++----- node-graph/gcore/src/registry.rs | 64 +- node-graph/gcore/src/transform.rs | 124 ++- node-graph/gcore/src/value.rs | 4 +- node-graph/gcore/src/vector/mod.rs | 6 +- .../src/vector/vector_data/attributes.rs | 76 +- .../src/vector/vector_data/modification.rs | 36 +- node-graph/node-macro/src/codegen.rs | 41 +- node-graph/node-macro/src/parsing.rs | 9 +- 15 files changed, 1083 insertions(+), 1174 deletions(-) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index efd08b94a2..0d456b7215 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -3,7 +3,7 @@ use core::borrow::Borrow; use crate::transform::Footprint; use dyn_any::DynAny; -pub trait Ctx {} +pub trait Ctx: Copy + Send {} pub trait ExtractFootprint: Ctx { fn footprint(&self) -> Option<&Footprint>; @@ -29,24 +29,26 @@ pub enum VarArgsResult { IndexOutOfBounds, NoVarArgs, } -impl Ctx for Option<&T> {} +impl Ctx for Option<&T> {} +impl Ctx for () {} +impl Ctx for Footprint {} -impl ExtractFootprint for Option<&T> { +impl ExtractFootprint for Option<&T> { fn footprint(&self) -> Option<&Footprint> { self.and_then(|x| x.footprint()) } } -impl ExtractTime for Option<&T> { +impl ExtractTime for Option<&T> { fn time(&self) -> Option { self.and_then(|x| x.time()) } } -impl ExtractIndex for Option<&T> { +impl ExtractIndex for Option<&T> { fn index(&self) -> Option { self.and_then(|x| x.index()) } } -impl ExtractVarArgs for Option<&T> { +impl ExtractVarArgs for Option<&T> { fn vararg<'b>(&'b self, index: usize) -> Result, VarArgsResult> where Self: 'b, @@ -88,7 +90,7 @@ type DynRef<'a> = &'a (dyn DynAny<'a> + 'a + Send + Sync); #[derive(Default, Clone, Copy, dyn_any::DynAny)] pub struct ContextImpl<'a> { - footprint: Option<&'a crate::transform::Footprint>, + pub(crate) footprint: Option<&'a crate::transform::Footprint>, varargs: Option<&'a [DynRef<'a>]>, // This could be converted into a single enum to save extra bytes index: Option, diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 967da9f42a..865dffd136 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -231,200 +231,200 @@ impl ArtboardGroup { } } -#[node_macro::node(category(""))] -async fn layer( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, - )] - stack: impl Node, - #[implementations( - () -> GraphicElement, - Footprint -> GraphicElement, - )] - element: impl Node, - node_path: Vec, -) -> GraphicGroup { - let mut element = element.eval(footprint).await; - let mut stack = stack.eval(footprint).await; - if stack.transform.matrix2.determinant() != 0. { - *element.transform_mut() = stack.transform.inverse() * element.transform(); - } else { - stack.clear(); - stack.transform = DAffine2::IDENTITY; - } - - // Get the penultimate element of the node path, or None if the path is too short - let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); - stack.push((element, encapsulating_node_id)); - stack -} - -#[node_macro::node(category("Debug"))] -async fn to_element + 'n>( - #[implementations( - (), - (), - (), - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> TextureFrame, - )] - data: impl Node, -) -> GraphicElement { - data.eval(footprint).await.into() -} - -#[node_macro::node(category("General"))] -async fn to_group + 'n>( - #[implementations( - (), - (), - (), - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> TextureFrame, - )] - element: impl Node, -) -> GraphicGroup { - element.eval(footprint).await.into() -} - -#[node_macro::node(category("General"))] -async fn flatten_group( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, - )] - group: impl Node, - fully_flatten: bool, -) -> GraphicGroup { - let nested_group = group.eval(footprint).await; - let mut flat_group = GraphicGroup::EMPTY; - fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) { - let mut collection_group = GraphicGroup::EMPTY; - for (element, reference) in current_group.elements { - if let GraphicElement::GraphicGroup(mut nested_group) = element { - nested_group.transform *= current_group.transform; - let mut sub_group = GraphicGroup::EMPTY; - if fully_flatten { - flatten_group(&mut sub_group, nested_group, fully_flatten); - } else { - for (collection_element, _) in &mut nested_group.elements { - *collection_element.transform_mut() = nested_group.transform * collection_element.transform(); - } - sub_group = nested_group; - } - collection_group.append(&mut sub_group.elements); - } else { - collection_group.push((element, reference)); - } - } - - result_group.append(&mut collection_group.elements); - } - flatten_group(&mut flat_group, nested_group, fully_flatten); - flat_group -} - -#[node_macro::node(category(""))] -async fn to_artboard + 'n>( - #[implementations( - (), - (), - (), - (), - Footprint, - )] - mut footprint: F, - #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> TextureFrame, - )] - contents: impl Node, - label: String, - location: IVec2, - dimensions: IVec2, - background: Color, - clip: bool, -) -> Artboard { - footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2())); - let graphic_group = contents.eval(footprint).await; - - Artboard { - graphic_group: graphic_group.into(), - label, - location: location.min(location + dimensions), - dimensions: dimensions.abs(), - background, - clip, - } -} - -#[node_macro::node(category(""))] -async fn append_artboard( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> ArtboardGroup, - Footprint -> ArtboardGroup, - )] - artboards: impl Node, - #[implementations( - () -> Artboard, - Footprint -> Artboard, - )] - artboard: impl Node, - node_path: Vec, -) -> ArtboardGroup { - let artboard = artboard.eval(footprint).await; - let mut artboards = artboards.eval(footprint).await; - - // Get the penultimate element of the node path, or None if the path is too short - let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); - artboards.append_artboard(artboard, encapsulating_node_id); - - artboards -} +// #[node_macro::node(category(""))] +// async fn layer( +// #[implementations( +// (), +// Footprint, +// )] +// footprint: F, +// #[implementations( +// () -> GraphicGroup, +// Footprint -> GraphicGroup, +// )] +// stack: impl Node, +// #[implementations( +// () -> GraphicElement, +// Footprint -> GraphicElement, +// )] +// element: impl Node, +// node_path: Vec, +// ) -> GraphicGroup { +// let mut element = element.eval(footprint).await; +// let mut stack = stack.eval(footprint).await; +// if stack.transform.matrix2.determinant() != 0. { +// *element.transform_mut() = stack.transform.inverse() * element.transform(); +// } else { +// stack.clear(); +// stack.transform = DAffine2::IDENTITY; +// } + +// // Get the penultimate element of the node path, or None if the path is too short +// let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); +// stack.push((element, encapsulating_node_id)); +// stack +// } + +// #[node_macro::node(category("Debug"))] +// async fn to_element + 'n>( +// #[implementations( +// (), +// (), +// (), +// (), +// Footprint, +// )] +// footprint: F, +// #[implementations( +// () -> GraphicGroup, +// () -> VectorData, +// () -> ImageFrame, +// () -> TextureFrame, +// Footprint -> GraphicGroup, +// Footprint -> VectorData, +// Footprint -> ImageFrame, +// Footprint -> TextureFrame, +// )] +// data: impl Node, +// ) -> GraphicElement { +// data.eval(footprint).await.into() +// } + +// #[node_macro::node(category("General"))] +// async fn to_group + 'n>( +// #[implementations( +// (), +// (), +// (), +// (), +// Footprint, +// )] +// footprint: F, +// #[implementations( +// () -> GraphicGroup, +// () -> VectorData, +// () -> ImageFrame, +// () -> TextureFrame, +// Footprint -> GraphicGroup, +// Footprint -> VectorData, +// Footprint -> ImageFrame, +// Footprint -> TextureFrame, +// )] +// element: impl Node, +// ) -> GraphicGroup { +// element.eval(footprint).await.into() +// } + +// #[node_macro::node(category("General"))] +// async fn flatten_group( +// #[implementations( +// (), +// Footprint, +// )] +// footprint: F, +// #[implementations( +// () -> GraphicGroup, +// Footprint -> GraphicGroup, +// )] +// group: impl Node, +// fully_flatten: bool, +// ) -> GraphicGroup { +// let nested_group = group.eval(footprint).await; +// let mut flat_group = GraphicGroup::EMPTY; +// fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) { +// let mut collection_group = GraphicGroup::EMPTY; +// for (element, reference) in current_group.elements { +// if let GraphicElement::GraphicGroup(mut nested_group) = element { +// nested_group.transform *= current_group.transform; +// let mut sub_group = GraphicGroup::EMPTY; +// if fully_flatten { +// flatten_group(&mut sub_group, nested_group, fully_flatten); +// } else { +// for (collection_element, _) in &mut nested_group.elements { +// *collection_element.transform_mut() = nested_group.transform * collection_element.transform(); +// } +// sub_group = nested_group; +// } +// collection_group.append(&mut sub_group.elements); +// } else { +// collection_group.push((element, reference)); +// } +// } + +// result_group.append(&mut collection_group.elements); +// } +// flatten_group(&mut flat_group, nested_group, fully_flatten); +// flat_group +// } + +// #[node_macro::node(category(""))] +// async fn to_artboard + 'n>( +// #[implementations( +// (), +// (), +// (), +// (), +// Footprint, +// )] +// mut footprint: F, +// #[implementations( +// () -> GraphicGroup, +// () -> VectorData, +// () -> ImageFrame, +// () -> TextureFrame, +// Footprint -> GraphicGroup, +// Footprint -> VectorData, +// Footprint -> ImageFrame, +// Footprint -> TextureFrame, +// )] +// contents: impl Node, +// label: String, +// location: IVec2, +// dimensions: IVec2, +// background: Color, +// clip: bool, +// ) -> Artboard { +// footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2())); +// let graphic_group = contents.eval(footprint).await; + +// Artboard { +// graphic_group: graphic_group.into(), +// label, +// location: location.min(location + dimensions), +// dimensions: dimensions.abs(), +// background, +// clip, +// } +// } + +// #[node_macro::node(category(""))] +// async fn append_artboard( +// #[implementations( +// (), +// Footprint, +// )] +// footprint: F, +// #[implementations( +// () -> ArtboardGroup, +// Footprint -> ArtboardGroup, +// )] +// artboards: impl Node, +// #[implementations( +// () -> Artboard, +// Footprint -> Artboard, +// )] +// artboard: impl Node, +// node_path: Vec, +// ) -> ArtboardGroup { +// let artboard = artboard.eval(footprint).await; +// let mut artboards = artboards.eval(footprint).await; + +// // Get the penultimate element of the node path, or None if the path is too short +// let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); +// artboards.append_artboard(artboard, encapsulating_node_id); + +// artboards +// } impl From> for GraphicElement { fn from(image_frame: ImageFrame) -> Self { diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index b69703194d..8672f85620 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -16,7 +16,7 @@ pub use ctor; pub mod consts; pub mod context; pub mod generic; -pub mod logic; +// pub mod logic; pub mod ops; pub mod structural; #[cfg(feature = "std")] @@ -57,7 +57,7 @@ pub use types::Cow; // pub trait Node: for<'n> NodeIO<'n> { /// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct. /// See `node-graph/README.md` for information on how to define a new node. -pub trait Node<'i, Input>: 'i { +pub trait Node<'i, Input> { type Output: 'i; /// Evaluates the node with the single specified input. fn eval(&'i self, input: Input) -> Self::Output; @@ -80,10 +80,10 @@ mod types; #[cfg(feature = "alloc")] pub use types::*; -pub trait NodeIO<'i, Input: 'i>: 'i + Node<'i, Input> +pub trait NodeIO<'i, Input>: Node<'i, Input> where Self::Output: 'i + StaticTypeSized, - Input: 'i + StaticTypeSized, + Input: StaticTypeSized, { fn input_type(&self) -> TypeId { TypeId::of::() @@ -123,7 +123,7 @@ where impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N where N::Output: 'i + StaticTypeSized, - I: 'i + StaticTypeSized, + I: StaticTypeSized, { } @@ -153,13 +153,13 @@ use dyn_any::StaticTypeSized; use core::pin::Pin; #[cfg(feature = "alloc")] -impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin + 'i>> { +impl<'i, I, O: 'i> Node<'i, I> for Pin + 'i>> { type Output = O; fn eval(&'i self, input: I) -> O { (**self).eval(input) } } -impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> { +impl<'i, I, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> { type Output = O; fn eval(&'i self, input: I) -> O { (**self).eval(input) diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 814a3ff5e1..cad2842372 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -1,54 +1,37 @@ -use crate::transform::Footprint; use crate::vector::VectorData; +use crate::Context; use glam::{DAffine2, DVec2}; #[node_macro::node(category("Debug"))] -async fn log_to_console( - #[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F, - #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2, - )] - value: impl Node, -) -> T { +fn log_to_console(#[implementations(Context)] _: F, #[implementations(String, bool, f64, u32, u64, DVec2, VectorData, DAffine2)] value: T) -> T { #[cfg(not(target_arch = "spirv"))] // KEEP THIS `debug!()` - It acts as the output for the debug node itself - let value = value.eval(footprint).await; debug!("{:#?}", value); value } #[node_macro::node(category("Debug"))] -async fn to_string( - #[implementations((), (), (), (), (), (), Footprint)] footprint: F, - #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, - )] - value: impl Node, -) -> String { - let value = value.eval(footprint).await; +fn to_string(#[implementations(Context)] _: F, #[implementations(String, bool, f64, u32, u64, DVec2, VectorData, DAffine2)] value: T) -> String { format!("{:?}", value) } #[node_macro::node(category("Debug"))] async fn switch( - #[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F, + #[implementations(Context)] footprint: F, condition: bool, #[expose] #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2 + Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorData, Context -> DAffine2, )] if_true: impl Node, #[expose] #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorData, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorData, Footprint -> DAffine2 + Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorData, Context -> DAffine2, )] if_false: impl Node, ) -> T { if condition { + // we can't remove these calls because we only want to evaluate the brach that we actually need if_true.eval(footprint).await } else { if_false.eval(footprint).await diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index ca8a3296db..026d2aac0f 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -1,9 +1,10 @@ -use crate::raster::BlendMode; -use crate::raster::ImageFrame; +// use crate::raster::BlendMode; +// use crate::raster::ImageFrame; use crate::registry::types::Percentage; -use crate::vector::style::GradientStops; +// use crate::vector::style::GradientStops; use crate::Ctx; -use crate::{Color, Node}; +// use crate::{Color, Node}; +use crate::Node; use math_parser::ast; use math_parser::context::{EvalContext, NothingMap, ValueProvider}; @@ -37,457 +38,457 @@ impl ValueProvider for MathNodeContext { } } -/// Calculates a mathematical expression with input values "A" and "B" -#[node_macro::node(category("Math"))] -fn math( - _: impl Ctx, - /// The value of "A" when calculating the expression - #[implementations(f64, f32)] - operand_a: U, - /// A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2" - #[default(A + B)] - expression: String, - /// The value of "B" when calculating the expression - #[implementations(f64, f32)] - #[default(1.)] - operand_b: U, -) -> U { - let (node, _unit) = match ast::Node::try_parse_from_str(&expression) { - Ok(expr) => expr, - Err(e) => { - warn!("Invalid expression: `{expression}`\n{e:?}"); - return U::from(0.).unwrap(); - } - }; - let context = EvalContext::new( - MathNodeContext { - a: operand_a.to_f64().unwrap(), - b: operand_b.to_f64().unwrap(), - }, - NothingMap, - ); - - let value = match node.eval(&context) { - Ok(value) => value, - Err(e) => { - warn!("Expression evaluation error: {e:?}"); - return U::from(0.).unwrap(); - } - }; - - let Value::Number(num) = value; - match num { - Number::Real(val) => U::from(val).unwrap(), - Number::Complex(c) => U::from(c.re).unwrap(), - } -} +// /// Calculates a mathematical expression with input values "A" and "B" +// #[node_macro::node(category("Math"))] +// fn math( +// _: impl Ctx, +// /// The value of "A" when calculating the expression +// #[implementations(f64, f32)] +// operand_a: U, +// /// A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2" +// #[default(A + B)] +// expression: String, +// /// The value of "B" when calculating the expression +// #[implementations(f64, f32)] +// #[default(1.)] +// operand_b: U, +// ) -> U { +// let (node, _unit) = match ast::Node::try_parse_from_str(&expression) { +// Ok(expr) => expr, +// Err(e) => { +// warn!("Invalid expression: `{expression}`\n{e:?}"); +// return U::from(0.).unwrap(); +// } +// }; +// let context = EvalContext::new( +// MathNodeContext { +// a: operand_a.to_f64().unwrap(), +// b: operand_b.to_f64().unwrap(), +// }, +// NothingMap, +// ); + +// let value = match node.eval(&context) { +// Ok(value) => value, +// Err(e) => { +// warn!("Expression evaluation error: {e:?}"); +// return U::from(0.).unwrap(); +// } +// }; + +// let Value::Number(num) = value; +// match num { +// Number::Real(val) => U::from(val).unwrap(), +// Number::Complex(c) => U::from(c.re).unwrap(), +// } +// } /// The addition operation (+) calculates the sum of two numbers. #[node_macro::node(category("Math: Arithmetic"))] fn add, T>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U, #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T, ) -> >::Output { augend + addend } -/// The subtraction operation (-) calculates the difference between two numbers. -#[node_macro::node(category("Math: Arithmetic"))] -fn subtract, T>( - _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U, - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T, -) -> >::Output { - minuend - subtrahend -} - -/// The multiplication operation (×) calculates the product of two numbers. -#[node_macro::node(category("Math: Arithmetic"))] -fn multiply, T>( - _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U, - #[default(1.)] - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] - multiplicand: T, -) -> >::Output { - multiplier * multiplicand -} - -/// The division operation (÷) calculates the quotient of two numbers. -#[node_macro::node(category("Math: Arithmetic"))] -fn divide, T>( - _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, - #[default(1.)] - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] - denominator: T, -) -> >::Output { - numerator / denominator -} - -/// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled. -#[node_macro::node(category("Math: Arithmetic"))] -fn modulo>>, T: Copy>( - _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, - #[default(2.)] - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] - modulus: T, - always_positive: bool, -) -> >::Output { - if always_positive { - (numerator % modulus + modulus) % modulus - } else { - numerator % modulus - } -} - -/// The exponent operation (^) calculates the result of raising a number to a power. -#[node_macro::node(category("Math: Arithmetic"))] -fn exponent, T>( - _: (), - #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U, - #[default(2.)] - #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)] - power: T, -) -> >::Output { - base.pow(power) -} - -/// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n. -#[node_macro::node(category("Math: Arithmetic"))] -fn root( - _: (), - #[default(2.)] - #[implementations(f64, f32)] - radicand: U, - #[default(2.)] - #[implementations(f64, f32)] - degree: U, -) -> U { - if degree == U::from(2.).unwrap() { - radicand.sqrt() - } else if degree == U::from(3.).unwrap() { - radicand.cbrt() - } else { - radicand.powf(U::from(1.).unwrap() / degree) - } -} - -/// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e". -#[node_macro::node(category("Math: Arithmetic"))] -fn logarithm( - _: (), - #[implementations(f64, f32)] value: U, - #[default(2.)] - #[implementations(f64, f32)] - base: U, -) -> U { - if base == U::from(2.).unwrap() { - value.log2() - } else if base == U::from(10.).unwrap() { - value.log10() - } else if base - U::from(std::f64::consts::E).unwrap() < U::epsilon() * U::from(1e6).unwrap() { - value.ln() - } else { - value.log(base) - } -} - -/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length. -#[node_macro::node(category("Math: Trig"))] -fn sine(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U { - if radians { - theta.sin() - } else { - theta.to_radians().sin() - } -} - -/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length. -#[node_macro::node(category("Math: Trig"))] -fn cosine(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U { - if radians { - theta.cos() - } else { - theta.to_radians().cos() - } -} - -/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length. -#[node_macro::node(category("Math: Trig"))] -fn tangent(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U { - if radians { - theta.tan() - } else { - theta.to_radians().tan() - } -} - -/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value. -#[node_macro::node(category("Math: Trig"))] -fn sine_inverse(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U { - if radians { - value.asin() - } else { - value.asin().to_degrees() - } -} - -/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value. -#[node_macro::node(category("Math: Trig"))] -fn cosine_inverse(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U { - if radians { - value.acos() - } else { - value.acos().to_degrees() - } -} - -/// The inverse tangent trigonometric function (atan) calculates the angle whose tangent is the specified value. -#[node_macro::node(category("Math: Trig"))] -fn tangent_inverse(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U { - if radians { - value.atan() - } else { - value.atan().to_degrees() - } -} - -/// The inverse tangent trigonometric function (atan2) calculates the angle whose tangent is the ratio of the two specified values. -#[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))] -fn tangent_inverse_2_argument( - _: (), - #[implementations(f64, f32)] y: U, - #[expose] - #[implementations(f64, f32)] - x: U, - radians: bool, -) -> U { - if radians { - y.atan2(x) - } else { - y.atan2(x).to_degrees() - } -} - -/// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed. -#[node_macro::node(category("Math: Numeric"))] -fn random( - _: (), - _primary: (), - seed: u64, - #[implementations(f64, f32)] - #[default(0.)] - min: U, - #[implementations(f64, f32)] - #[default(1.)] - max: U, -) -> f64 { - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - let result = rng.gen::(); - let (min, max) = if min < max { (min, max) } else { (max, min) }; - let (min, max) = (min.to_f64().unwrap(), max.to_f64().unwrap()); - result * (max - min) + min -} - -/// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. -#[node_macro::node(name("To u32"), category("Math: Numeric"))] -fn to_u32(_: (), #[implementations(f64, f32)] value: U) -> u32 { - let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap()); - value.to_u32().unwrap() -} - -/// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. -#[node_macro::node(name("To u64"), category("Math: Numeric"))] -fn to_u64(_: (), #[implementations(f64, f32)] value: U) -> u64 { - let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap()); - value.to_u64().unwrap() -} - -/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero. -#[node_macro::node(category("Math: Numeric"))] -fn round(_: (), #[implementations(f64, f32)] value: U) -> U { - value.round() -} - -/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole. -#[node_macro::node(category("Math: Numeric"))] -fn floor(_: (), #[implementations(f64, f32)] value: U) -> U { - value.floor() -} - -/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole. -#[node_macro::node(category("Math: Numeric"))] -fn ceiling(_: (), #[implementations(f64, f32)] value: U) -> U { - value.ceil() -} - -/// The absolute value function (abs) removes the negative sign from an input value, if present. -#[node_macro::node(category("Math: Numeric"))] -fn absolute_value(_: (), #[implementations(f64, f32)] value: U) -> U { - value.abs() -} - -/// The minimum function (min) picks the smaller of two numbers. -#[node_macro::node(category("Math: Numeric"))] -fn min(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { - match value < other_value { - true => value, - false => other_value, - } -} - -/// The maximum function (max) picks the larger of two numbers. -#[node_macro::node(category("Math: Numeric"))] -fn max(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { - match value > other_value { - true => value, - false => other_value, - } -} - -/// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed. -#[node_macro::node(category("Math: Numeric"))] -fn clamp( - _: (), - #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, - #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T, - #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T, -) -> T { - let (min, max) = if min < max { (min, max) } else { (max, min) }; - if value < min { - min - } else if value > max { - max - } else { - value - } -} - -/// The equality operation (==) compares two values and returns true if they are equal, or false if they are not. -#[node_macro::node(category("Math: Logic"))] -fn equals, T>( - _: (), - #[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 -} - -/// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are. -#[node_macro::node(category("Math: Logic"))] -fn not_equals, T>( - _: (), - #[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 -} - -/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false. -#[node_macro::node(category("Math: Logic"))] -fn logical_or(_: (), value: bool, other_value: bool) -> bool { - value || other_value -} - -/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false. -#[node_macro::node(category("Math: Logic"))] -fn logical_and(_: (), value: bool, other_value: bool) -> bool { - value && other_value -} - -/// The logical not operation (!) reverses true and false value of the input. -#[node_macro::node(category("Math: Logic"))] -fn logical_not(_: (), input: bool) -> bool { - !input -} - -/// Constructs a bool value which may be set to true or false. -#[node_macro::node(category("Value"))] -fn bool_value(_: (), _primary: (), #[name("Bool")] bool_value: bool) -> bool { - bool_value -} - -/// Constructs a number value which may be set to any real number. -#[node_macro::node(category("Value"))] -fn number_value(_: (), _primary: (), number: f64) -> f64 { - number -} - -/// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider. -#[node_macro::node(category("Value"))] -fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 { - percentage -} - -/// Constructs a two-dimensional vector value which may be set to any XY coordinate. -#[node_macro::node(category("Value"))] -fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> DVec2 { - DVec2::new(x, y) -} - -/// Constructs a color value which may be set to any color, or no color. -#[node_macro::node(category("Value"))] -fn color_value(_: (), _primary: (), #[default(Color::BLACK)] color: Option) -> Option { - color -} - -/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. -#[node_macro::node(category("Value"))] -fn gradient_value(_: (), _primary: (), gradient: GradientStops) -> GradientStops { - gradient -} - -/// Constructs a blend mode choice value which may be set to any of the available blend modes in order to tell another node which blending operation to use. -#[node_macro::node(category("Value"))] -fn blend_mode_value(_: (), _primary: (), blend_mode: BlendMode) -> BlendMode { - blend_mode -} - -/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes. -#[cfg(feature = "std")] -#[node_macro::node(category("Debug"))] -fn size_of(_: (), ty: crate::Type) -> Option { - ty.size() -} - -/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option. -#[node_macro::node(category("Debug"))] -fn some(_: (), #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option { - Some(input) -} - -/// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None. -#[node_macro::node(category("Debug"))] -fn unwrap(_: (), #[implementations(Option, Option, Option, Option, Option, Option)] input: Option) -> T { - input.unwrap_or_default() -} - -/// Meant for debugging purposes, not general use. Clones the input value. -#[node_macro::node(category("Debug"))] -fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrame)] value: &'i T) -> T { - value.clone() -} - -#[node_macro::node(category("Math: Vector"))] -fn dot_product(vector_a: DVec2, vector_b: DVec2) -> f64 { - vector_a.dot(vector_b) -} - -// TODO: Rename to "Passthrough" -/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. -#[node_macro::node(skip_impl)] -fn identity<'i, T: 'i>(value: T) -> T { - value -} +// /// The subtraction operation (-) calculates the difference between two numbers. +// #[node_macro::node(category("Math: Arithmetic"))] +// fn subtract, T>( +// _: impl Ctx, +// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U, +// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T, +// ) -> >::Output { +// minuend - subtrahend +// } + +// /// The multiplication operation (×) calculates the product of two numbers. +// #[node_macro::node(category("Math: Arithmetic"))] +// fn multiply, T>( +// _: impl Ctx, +// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U, +// #[default(1.)] +// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] +// multiplicand: T, +// ) -> >::Output { +// multiplier * multiplicand +// } + +// /// The division operation (÷) calculates the quotient of two numbers. +// #[node_macro::node(category("Math: Arithmetic"))] +// fn divide, T>( +// _: impl Ctx, +// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, +// #[default(1.)] +// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] +// denominator: T, +// ) -> >::Output { +// numerator / denominator +// } + +// /// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled. +// #[node_macro::node(category("Math: Arithmetic"))] +// fn modulo>>, T: Copy>( +// _: impl Ctx, +// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, +// #[default(2.)] +// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] +// modulus: T, +// always_positive: bool, +// ) -> >::Output { +// if always_positive { +// (numerator % modulus + modulus) % modulus +// } else { +// numerator % modulus +// } +// } + +// /// The exponent operation (^) calculates the result of raising a number to a power. +// #[node_macro::node(category("Math: Arithmetic"))] +// fn exponent, T>( +// _: impl Ctx, +// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U, +// #[default(2.)] +// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)] +// power: T, +// ) -> >::Output { +// base.pow(power) +// } + +// /// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n. +// #[node_macro::node(category("Math: Arithmetic"))] +// fn root( +// _: impl Ctx, +// #[default(2.)] +// #[implementations(f64, f32)] +// radicand: U, +// #[default(2.)] +// #[implementations(f64, f32)] +// degree: U, +// ) -> U { +// if degree == U::from(2.).unwrap() { +// radicand.sqrt() +// } else if degree == U::from(3.).unwrap() { +// radicand.cbrt() +// } else { +// radicand.powf(U::from(1.).unwrap() / degree) +// } +// } + +// /// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e". +// #[node_macro::node(category("Math: Arithmetic"))] +// fn logarithm( +// _: impl Ctx, +// #[implementations(f64, f32)] value: U, +// #[default(2.)] +// #[implementations(f64, f32)] +// base: U, +// ) -> U { +// if base == U::from(2.).unwrap() { +// value.log2() +// } else if base == U::from(10.).unwrap() { +// value.log10() +// } else if base - U::from(std::f64::consts::E).unwrap() < U::epsilon() * U::from(1e6).unwrap() { +// value.ln() +// } else { +// value.log(base) +// } +// } + +// /// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length. +// #[node_macro::node(category("Math: Trig"))] +// fn sine(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { +// if radians { +// theta.sin() +// } else { +// theta.to_radians().sin() +// } +// } + +// /// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length. +// #[node_macro::node(category("Math: Trig"))] +// fn cosine(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { +// if radians { +// theta.cos() +// } else { +// theta.to_radians().cos() +// } +// } + +// /// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length. +// #[node_macro::node(category("Math: Trig"))] +// fn tangent(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { +// if radians { +// theta.tan() +// } else { +// theta.to_radians().tan() +// } +// } + +// /// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value. +// #[node_macro::node(category("Math: Trig"))] +// fn sine_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { +// if radians { +// value.asin() +// } else { +// value.asin().to_degrees() +// } +// } + +// /// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value. +// #[node_macro::node(category("Math: Trig"))] +// fn cosine_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { +// if radians { +// value.acos() +// } else { +// value.acos().to_degrees() +// } +// } + +// /// The inverse tangent trigonometric function (atan) calculates the angle whose tangent is the specified value. +// #[node_macro::node(category("Math: Trig"))] +// fn tangent_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { +// if radians { +// value.atan() +// } else { +// value.atan().to_degrees() +// } +// } + +// /// The inverse tangent trigonometric function (atan2) calculates the angle whose tangent is the ratio of the two specified values. +// #[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))] +// fn tangent_inverse_2_argument( +// _: impl Ctx, +// #[implementations(f64, f32)] y: U, +// #[expose] +// #[implementations(f64, f32)] +// x: U, +// radians: bool, +// ) -> U { +// if radians { +// y.atan2(x) +// } else { +// y.atan2(x).to_degrees() +// } +// } + +// /// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed. +// #[node_macro::node(category("Math: Numeric"))] +// fn random( +// _: impl Ctx, +// _primary: (), +// seed: u64, +// #[implementations(f64, f32)] +// #[default(0.)] +// min: U, +// #[implementations(f64, f32)] +// #[default(1.)] +// max: U, +// ) -> f64 { +// let mut rng = rand::rngs::StdRng::seed_from_u64(seed); +// let result = rng.gen::(); +// let (min, max) = if min < max { (min, max) } else { (max, min) }; +// let (min, max) = (min.to_f64().unwrap(), max.to_f64().unwrap()); +// result * (max - min) + min +// } + +// /// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. +// #[node_macro::node(name("To u32"), category("Math: Numeric"))] +// fn to_u32(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u32 { +// let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap()); +// value.to_u32().unwrap() +// } + +// /// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. +// #[node_macro::node(name("To u64"), category("Math: Numeric"))] +// fn to_u64(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u64 { +// let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap()); +// value.to_u64().unwrap() +// } + +// /// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero. +// #[node_macro::node(category("Math: Numeric"))] +// fn round(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { +// value.round() +// } + +// /// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole. +// #[node_macro::node(category("Math: Numeric"))] +// fn floor(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { +// value.floor() +// } + +// /// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole. +// #[node_macro::node(category("Math: Numeric"))] +// fn ceiling(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { +// value.ceil() +// } + +// /// The absolute value function (abs) removes the negative sign from an input value, if present. +// #[node_macro::node(category("Math: Numeric"))] +// fn absolute_value(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { +// value.abs() +// } + +// /// The minimum function (min) picks the smaller of two numbers. +// #[node_macro::node(category("Math: Numeric"))] +// fn min(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { +// match value < other_value { +// true => value, +// false => other_value, +// } +// } + +// /// The maximum function (max) picks the larger of two numbers. +// #[node_macro::node(category("Math: Numeric"))] +// fn max(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { +// match value > other_value { +// true => value, +// false => other_value, +// } +// } + +// /// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed. +// #[node_macro::node(category("Math: Numeric"))] +// fn clamp( +// _: impl Ctx, +// #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, +// #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T, +// #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T, +// ) -> T { +// let (min, max) = if min < max { (min, max) } else { (max, min) }; +// if value < min { +// min +// } else if value > max { +// max +// } else { +// value +// } +// } + +// /// The equality operation (==) compares two values and returns true if they are equal, or false if they are not. +// #[node_macro::node(category("Math: Logic"))] +// 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 +// } + +// /// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are. +// #[node_macro::node(category("Math: Logic"))] +// 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 +// } + +// /// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false. +// #[node_macro::node(category("Math: Logic"))] +// fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool { +// value || other_value +// } + +// /// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false. +// #[node_macro::node(category("Math: Logic"))] +// fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool { +// value && other_value +// } + +// /// The logical not operation (!) reverses true and false value of the input. +// #[node_macro::node(category("Math: Logic"))] +// fn logical_not(_: impl Ctx, input: bool) -> bool { +// !input +// } + +// /// Constructs a bool value which may be set to true or false. +// #[node_macro::node(category("Value"))] +// fn bool_value(_: impl Ctx, _primary: (), #[name("Bool")] bool_value: bool) -> bool { +// bool_value +// } + +// /// Constructs a number value which may be set to any real number. +// #[node_macro::node(category("Value"))] +// fn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 { +// number +// } + +// /// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider. +// #[node_macro::node(category("Value"))] +// fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 { +// percentage +// } + +// /// Constructs a two-dimensional vector value which may be set to any XY coordinate. +// #[node_macro::node(category("Value"))] +// fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 { +// DVec2::new(x, y) +// } + +// /// Constructs a color value which may be set to any color, or no color. +// #[node_macro::node(category("Value"))] +// fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option) -> Option { +// color +// } + +// /// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. +// #[node_macro::node(category("Value"))] +// fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops { +// gradient +// } + +// /// Constructs a blend mode choice value which may be set to any of the available blend modes in order to tell another node which blending operation to use. +// #[node_macro::node(category("Value"))] +// fn blend_mode_value(_: impl Ctx, _primary: (), blend_mode: BlendMode) -> BlendMode { +// blend_mode +// } + +// /// Meant for debugging purposes, not general use. Returns the size of the input type in bytes. +// #[cfg(feature = "std")] +// #[node_macro::node(category("Debug"))] +// fn size_of(_: impl Ctx, ty: crate::Type) -> Option { +// ty.size() +// } + +// /// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option. +// #[node_macro::node(category("Debug"))] +// fn some(_: impl Ctx, #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option { +// Some(input) +// } + +// /// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None. +// #[node_macro::node(category("Debug"))] +// fn unwrap(_: impl Ctx, #[implementations(Option, Option, Option, Option, Option, Option)] input: Option) -> T { +// input.unwrap_or_default() +// } + +// /// Meant for debugging purposes, not general use. Clones the input value. +// #[node_macro::node(category("Debug"))] +// fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&ImageFrame)] value: &'i T) -> T { +// value.clone() +// } + +// #[node_macro::node(category("Math: Vector"))] +// fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 { +// vector_a.dot(vector_b) +// } + +// // TODO: Rename to "Passthrough" +// /// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. +// #[node_macro::node(skip_impl)] +// fn identity<'i, T: 'i>(value: T) -> T { +// value +// } // Type // TODO: Document this @@ -535,51 +536,51 @@ where input.into() } -#[cfg(test)] -mod test { - use super::*; - use crate::{generic::*, structural::*, value::*}; - - #[test] - pub fn dot_product_function() { - let vector_a = glam::DVec2::new(1., 2.); - let vector_b = glam::DVec2::new(3., 4.); - assert_eq!(dot_product(vector_a, vector_b), 11.); - } - - #[test] - fn test_basic_expression() { - let result = math((), 0., "2 + 2".to_string(), 0.); - assert_eq!(result, 4.); - } - - #[test] - fn test_complex_expression() { - let result = math((), 0., "(5 * 3) + (10 / 2)".to_string(), 0.); - assert_eq!(result, 20.); - } - - #[test] - fn test_default_expression() { - let result = math((), 0., "0".to_string(), 0.); - assert_eq!(result, 0.); - } - - #[test] - fn test_invalid_expression() { - let result = math((), 0., "invalid".to_string(), 0.); - assert_eq!(result, 0.); - } - - #[test] - pub fn identity_node() { - let value = ValueNode(4u32).then(IdentityNode::new()); - assert_eq!(value.eval(()), &4); - } - - #[test] - pub fn foo() { - let fnn = FnNode::new(|(a, b)| (b, a)); - assert_eq!(fnn.eval((1u32, 2u32)), (2, 1)); - } -} +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::{generic::*, structural::*, value::*}; + +// #[test] +// pub fn dot_product_function() { +// let vector_a = glam::DVec2::new(1., 2.); +// let vector_b = glam::DVec2::new(3., 4.); +// assert_eq!(dot_product(vector_a, vector_b), 11.); +// } + +// #[test] +// fn test_basic_expression() { +// let result = math((), 0., "2 + 2".to_string(), 0.); +// assert_eq!(result, 4.); +// } + +// #[test] +// fn test_complex_expression() { +// let result = math((), 0., "(5 * 3) + (10 / 2)".to_string(), 0.); +// assert_eq!(result, 20.); +// } + +// #[test] +// fn test_default_expression() { +// let result = math((), 0., "0".to_string(), 0.); +// assert_eq!(result, 0.); +// } + +// #[test] +// fn test_invalid_expression() { +// let result = math((), 0., "invalid".to_string(), 0.); +// assert_eq!(result, 0.); +// } + +// #[test] +// pub fn identity_node() { +// let value = ValueNode(4u32).then(IdentityNode::new()); +// assert_eq!(value.eval(()), &4); +// } + +// #[test] +// pub fn foo() { +// let fnn = FnNode::new(|(a, b)| (b, a)); +// assert_eq!(fnn.eval((1u32, 2u32)), (2, 1)); +// } +// } diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 6ea7139649..1ec17df1a1 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -1,7 +1,7 @@ pub use self::color::{Color, Luma, SRGBA8}; use crate::vector::VectorData; -use crate::GraphicGroup; use crate::{registry::types::Percentage, transform::Footprint}; +use crate::{Ctx, GraphicGroup}; use bytemuck::{Pod, Zeroable}; use core::fmt::Debug; @@ -308,51 +308,31 @@ impl SetBlendMode for ImageFrame { } #[node_macro::node(category("Style"))] -async fn blend_mode( +fn blend_mode( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + GraphicGroup, + VectorData, + ImageFrame, )] - footprint: F, - #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - )] - value: impl Node, + mut value: T, blend_mode: BlendMode, ) -> T { - let mut value = value.eval(footprint).await; value.set_blend_mode(blend_mode); value } #[node_macro::node(category("Style"))] -async fn opacity( - #[implementations( - (), - (), - (), - Footprint, - )] - footprint: F, +fn opacity( + _: impl Ctx, #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, + GraphicGroup, + VectorData, + ImageFrame, )] - value: impl Node, + mut value: T, #[default(100.)] factor: Percentage, ) -> T { - let mut value = value.eval(footprint).await; let opacity_multiplier = factor / 100.; value.multiply_alpha(opacity_multiplier); value diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 5440b0d69a..79feb9c18a 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -6,10 +6,10 @@ use super::curve::{Curve, CurveManipulatorGroup, ValueMapperNode}; use super::ImageFrame; use super::{Channel, Color, Pixel}; use crate::registry::types::{Angle, Percentage, SignedPercentage}; -use crate::transform::Footprint; use crate::vector::style::GradientStops; use crate::vector::VectorData; use crate::GraphicGroup; +use crate::{Context, Ctx}; use dyn_any::DynAny; @@ -284,26 +284,16 @@ impl From for vello::peniko::Mix { } #[node_macro::node(category("Raster: Adjustment"))] -async fn luminance>( +fn luminance>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, luminance_calc: LuminanceCalculation, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let luminance = match luminance_calc { LuminanceCalculation::SRGB => color.luminance_srgb(), @@ -318,26 +308,16 @@ async fn luminance>( } #[node_macro::node(category("Raster"))] -async fn extract_channel>( - #[implementations( - (), - (), - (), - Footprint, - )] - footprint: F, +fn extract_channel>( + _: impl Ctx, #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, + Color, + ImageFrame, + GradientStops, )] - input: impl Node, + mut input: T, channel: RedGreenBlueAlpha, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let extracted_value = match channel { RedGreenBlueAlpha::Red => color.r(), @@ -351,25 +331,15 @@ async fn extract_channel>( } #[node_macro::node(category("Raster"))] -async fn make_opaque>( +fn make_opaque>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { if color.a() == 0. { return color.with_alpha(1.); @@ -385,31 +355,21 @@ async fn make_opaque>( // Algorithm from: // https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels #[node_macro::node(category("Raster: Adjustment"))] -async fn levels>( +fn levels>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, #[default(0.)] shadows: Percentage, #[default(50.)] midtones: Percentage, #[default(100.)] highlights: Percentage, #[default(0.)] output_minimums: Percentage, #[default(100.)] output_maximums: Percentage, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let color = color.to_gamma_srgb(); // Input Range (Range: 0-1) @@ -451,7 +411,7 @@ async fn levels>( color.to_linear_srgb() }); - input + image } // Aims for interoperable compatibility with: @@ -462,23 +422,14 @@ async fn levels>( // https://stackoverflow.com/a/55233732/775283 // Works the same for gamma and linear color #[node_macro::node(name("Black & White"), category("Raster: Adjustment"))] -async fn black_and_white>( +async fn black_and_white>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, #[default(Color::BLACK)] tint: Color, #[default(40.)] #[range((-200., 300.))] @@ -499,8 +450,7 @@ async fn black_and_white>( #[range((-200., 300.))] magentas: Percentage, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let color = color.to_gamma_srgb(); let reds = reds as f32 / 100.; @@ -537,35 +487,25 @@ async fn black_and_white>( color.to_linear_srgb() }); - input + image } // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0 // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings #[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))] -async fn hue_saturation>( - #[implementations( - (), - (), - (), - Footprint, - )] - footprint: F, +async fn hue_saturation>( + _: impl Ctx, #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, + Color, + ImageFrame, + GradientStops, )] - input: impl Node, + mut input: T, hue_shift: Angle, saturation_shift: SignedPercentage, lightness_shift: SignedPercentage, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -588,25 +528,15 @@ async fn hue_saturation>( // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize #[node_macro::node(category("Raster: Adjustment"))] -async fn invert>( +async fn invert>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -620,29 +550,19 @@ async fn invert>( // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient #[node_macro::node(category("Raster: Adjustment"))] -async fn threshold>( - #[implementations( - (), - (), - (), - Footprint, - )] - footprint: F, +async fn threshold>( + _: impl Ctx, #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, + Color, + ImageFrame, + GradientStops, )] - image: impl Node, + mut image: T, #[default(50.)] min_luminance: Percentage, #[default(100.)] max_luminance: Percentage, luminance_calc: LuminanceCalculation, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.); let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.); @@ -660,7 +580,7 @@ async fn threshold>( Color::BLACK } }); - input + image } trait Blend { @@ -720,44 +640,31 @@ impl Blend for GradientStops { } #[node_macro::node(category("Raster"))] -async fn blend + Send>( +async fn blend + Send>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - over: impl Node, + over: T, #[expose] #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, + Color, + ImageFrame, + GradientStops, )] - under: impl Node, + under: T, blend_mode: BlendMode, #[default(100.)] opacity: Percentage, ) -> T { - let over = over.eval(footprint).await; - let under = under.eval(footprint).await; - Blend::blend(&over, &under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) } #[node_macro::node(category(""))] -fn blend_color_pair(input: (Color, Color), blend_mode: BlendMode, opacity: Percentage) -> Color { +async fn blend_color_pair(input: (Color, Color), blend_mode: impl Node, Output = BlendMode>, opacity: impl Node, Output = Percentage>) -> Color { + let blend_mode = blend_mode.eval(None).await; + let opacity = opacity.eval(None).await; blend_colors(input.0, input.1, blend_mode, opacity / 100.) } @@ -847,35 +754,24 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0) #[node_macro::node(category("Raster: Adjustment"))] -async fn gradient_map>( +async fn gradient_map>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, gradient: GradientStops, reverse: bool, ) -> T { - let mut input = image.eval(footprint).await; - - input.adjust(|color| { + image.adjust(|color| { let intensity = color.luminance_srgb(); let intensity = if reverse { 1. - intensity } else { intensity }; gradient.evalute(intensity as f64) }); - input + image } // Aims for interoperable compatibility with: @@ -886,27 +782,17 @@ async fn gradient_map>( // https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters // The results of this implementation are very close to correct, but not quite perfect #[node_macro::node(category("Raster: Adjustment"))] -async fn vibrance>( - #[implementations( - (), - (), - (), - Footprint, - )] - footprint: F, +async fn vibrance>( + _: impl Ctx, #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, + Color, + ImageFrame, + GradientStops, )] - image: impl Node, + mut image: T, vibrance: SignedPercentage, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let vibrance = vibrance as f32 / 100.; // Slow the effect down by half when it's negative, since artifacts begin appearing past -50%. // So this scales the 0% to -50% range to 0% to -100%. @@ -953,7 +839,7 @@ async fn vibrance>( altered_color.map_rgb(|c| c * (1. - factor) + luminance * factor) } }); - input + image } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -1186,23 +1072,14 @@ impl DomainWarpType { // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr #[node_macro::node(category("Raster: Adjustment"))] -async fn channel_mixer>( - #[implementations( - (), - (), - (), - Footprint, - )] - footprint: F, +async fn channel_mixer>( + _: impl Ctx, #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, + Color, + ImageFrame, + GradientStops, )] - image: impl Node, + mut image: T, monochrome: bool, #[default(40.)] @@ -1260,8 +1137,7 @@ async fn channel_mixer>( // Display-only properties (not used within the node) _output_channel: RedGreenBlue, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let color = color.to_gamma_srgb(); let (r, g, b, a) = color.components(); @@ -1286,7 +1162,7 @@ async fn channel_mixer>( color.to_linear_srgb() }); - input + image } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -1347,23 +1223,14 @@ impl core::fmt::Display for SelectiveColorChoice { // Algorithm based on: // https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html #[node_macro::node(category("Raster: Adjustment"))] -async fn selective_color>( +async fn selective_color>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, mode: RelativeAbsolute, #[name("(Reds) Cyan")] r_c: f64, #[name("(Reds) Magenta")] r_m: f64, @@ -1403,8 +1270,7 @@ async fn selective_color>( #[name("(Blacks) Black")] k_k: f64, _colors: SelectiveColorChoice, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let color = color.to_gamma_srgb(); let (r, g, b, a) = color.components(); @@ -1478,7 +1344,7 @@ async fn selective_color>( color.to_linear_srgb() }); - input + image } pub(super) trait MultiplyAlpha { @@ -1513,28 +1379,18 @@ impl MultiplyAlpha for ImageFrame

{ // https://www.axiomx.com/posterize.htm // This algorithm produces fully accurate output in relation to the industry standard. #[node_macro::node(category("Raster: Adjustment"))] -async fn posterize>( - #[implementations( - (), - (), - (), - Footprint, - )] - footprint: F, +async fn posterize>( + _: impl Ctx, #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, + Color, + ImageFrame, + GradientStops, )] - input: impl Node, + mut input: T, #[default(4)] #[min(2.)] levels: u32, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -1556,30 +1412,20 @@ async fn posterize>( // Algorithm based on: // https://geraldbakker.nl/psnumbers/exposure.html #[node_macro::node(category("Raster: Adjustment"))] -async fn exposure>( +async fn exposure>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrame, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, exposure: f64, offset: f64, #[default(1.)] #[range((0.01, 10.))] gamma_correction: f64, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let adjusted = color // Exposure @@ -1598,7 +1444,7 @@ const WINDOW_SIZE: usize = 1024; #[cfg(feature = "alloc")] #[node_macro::node(category(""))] -fn generate_curves(_: (), curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode { +fn generate_curves(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode { use bezier_rs::{Bezier, TValue}; let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle]; @@ -1639,31 +1485,21 @@ fn generate_curves(_: (), curve: Curve, #[implementa #[cfg(feature = "alloc")] #[node_macro::node(category("Raster: Adjustment"))] -async fn color_overlay>( - #[implementations( - (), - (), - (), - Footprint, - )] - footprint: F, +fn color_overlay>( + _: impl Ctx, #[implementations( - () -> Color, - () -> ImageFrame, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrame, - Footprint -> GradientStops, + Color, + ImageFrame, + GradientStops, )] - image: impl Node, + mut image: T, #[default(Color::BLACK)] color: Color, blend_mode: BlendMode, #[default(100.)] opacity: Percentage, ) -> T { let opacity = (opacity as f32 / 100.).clamp(0., 1.); - let mut input = image.eval(footprint).await; - input.adjust(|pixel| { + image.adjust(|pixel| { let image = pixel.map_rgb(|channel| channel * (1. - opacity)); // The apply blend mode function divides rgb by the alpha channel for the background. This undoes that. @@ -1672,7 +1508,7 @@ async fn color_overlay>( Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a()) }); - input + image } #[cfg(feature = "alloc")] @@ -1680,10 +1516,13 @@ pub use index_node::IndexNode; #[cfg(feature = "alloc")] mod index_node { - use crate::raster::{Color, ImageFrame}; + use crate::{ + raster::{Color, ImageFrame}, + Ctx, + }; #[node_macro::node(category(""))] - pub fn index(_: (), #[implementations(Vec>, Vec)] input: Vec, index: u32) -> T { + pub fn index(_: impl Ctx, #[implementations(Vec>, Vec)] input: Vec, index: u32) -> T { if (index as usize) < input.len() { input[index as usize].clone() } else { @@ -1724,7 +1563,7 @@ mod test { // 100% of the output should come from the multiplied value let opacity = 100_f64; - let result = super::color_overlay((), &FutureWrapperNode(image), overlay_color, BlendMode::Multiply, opacity).await; + let result = super::color_overlay((), image, overlay_color, BlendMode::Multiply, opacity); // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) assert_eq!(result.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a())); diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 3d1868d8e2..d5c76f2cdb 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -73,28 +73,28 @@ pub type Any<'n> = Box + 'n>; pub type FutureAny<'n> = DynFuture<'n, Any<'n>>; // TODO: is this safe? This is assumed to be send+sync. #[cfg(not(target_arch = "wasm32"))] -pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync; +pub type TypeErasedNode<'n, 'i, 's> = dyn NodeIO<'i, Any<'s>, Output = FutureAny<'i>> + 'n + Send + Sync; #[cfg(target_arch = "wasm32")] -pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n; -pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>; -pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>; -pub type TypeErasedBox<'n> = Box>; -pub type TypeErasedPinned<'n> = Pin>>; +pub type TypeErasedNode<'n, 'i, 's> = dyn NodeIO<'i, Any<'s>, Output = FutureAny<'i>> + 'n; +pub type TypeErasedPinnedRef<'n, 'i, 's> = Pin<&'n TypeErasedNode<'n, 'i, 's>>; +pub type TypeErasedRef<'n, 'i, 's> = &'n TypeErasedNode<'n, 'i, 's>; +pub type TypeErasedBox<'n, 'i, 's> = Box>; +pub type TypeErasedPinned<'n, 'i, 's> = Pin>>; -pub type SharedNodeContainer = std::sync::Arc; +pub type SharedNodeContainer<'n, 'i, 's> = std::sync::Arc>; -pub type NodeConstructor = fn(Vec) -> DynFuture<'static, TypeErasedBox<'static>>; +pub type NodeConstructor = for<'n, 'i, 's> fn(Vec>) -> DynFuture<'n, TypeErasedBox<'n, 'i, 's>>; #[derive(Clone)] -pub struct NodeContainer { +pub struct NodeContainer<'n, 'i, 's> { #[cfg(feature = "dealloc_nodes")] - pub node: *const TypeErasedNode<'static>, + pub node: *const TypeErasedNode<'n, 'i, 's>, #[cfg(not(feature = "dealloc_nodes"))] - pub node: TypeErasedRef<'static>, + pub node: TypeErasedRef<'n, 'i, 's>, } -impl Deref for NodeContainer { - type Target = TypeErasedNode<'static>; +impl<'n: 'i, 'i: 's, 's> Deref for NodeContainer<'n, 'i, 's> { + type Target = TypeErasedNode<'n, 'i, 's>; #[cfg(feature = "dealloc_nodes")] fn deref(&self) -> &Self::Target { @@ -112,35 +112,45 @@ impl Deref for NodeContainer { /// Marks NodeContainer as Sync. This dissallows the use of threadlocal storage for nodes as this would invalidate references to them. // TODO: implement this on a higher level wrapper to avoid missuse #[cfg(feature = "dealloc_nodes")] -unsafe impl Send for NodeContainer {} +unsafe impl<'n: 'i, 'i, 's> Send for NodeContainer<'n, 'i, 's> {} #[cfg(feature = "dealloc_nodes")] -unsafe impl Sync for NodeContainer {} +unsafe impl<'n: 'i, 'i, 's> Sync for NodeContainer<'n, 'i, 's> {} #[cfg(feature = "dealloc_nodes")] -impl Drop for NodeContainer { +impl<'n, 'i, 's> Drop for NodeContainer<'n, 'i, 's> { fn drop(&mut self) { unsafe { self.dealloc_unchecked() } } } -impl core::fmt::Debug for NodeContainer { +impl<'n: 'i, 'i: 's, 's> core::fmt::Debug for NodeContainer<'n, 'i, 's> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("NodeContainer").finish() } } -impl NodeContainer { - pub fn new(node: TypeErasedBox<'static>) -> SharedNodeContainer { +impl<'n: 'i, 'i: 's, 's> NodeContainer<'n, 'i, 's> { + pub fn new(node: TypeErasedBox<'n, 'i, 's>) -> SharedNodeContainer<'n, 'i, 's> { let node = Box::leak(node); Self { node }.into() } +} +impl<'n, 'i, 's> NodeContainer<'n, 'i, 's> { #[cfg(feature = "dealloc_nodes")] unsafe fn dealloc_unchecked(&mut self) { std::mem::drop(Box::from_raw(self.node as *mut TypeErasedNode)); } } +impl<'n: 'i, 'i: 's, 's> Node<'i, Any<'s>> for SharedNodeContainer<'n, 'i, 's> { + type Output = FutureAny<'i>; + + fn eval(&'i self, input: Any<'s>) -> Self::Output { + (**self).eval(input) + } +} + use crate::Node; use crate::WasmNotSend; use dyn_any::StaticType; @@ -149,12 +159,12 @@ use std::marker::PhantomData; /// Boxes the input and downcasts the output. /// Wraps around a node taking Box and returning Box #[derive(Clone)] -pub struct DowncastBothNode { - node: SharedNodeContainer, +pub struct DowncastBothNode<'n, 'i, 's, I, O> { + node: SharedNodeContainer<'n, 'i, 's>, _i: PhantomData, _o: PhantomData, } -impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode { +impl<'input: 's, 'n: 'input, 's, O: 'input + StaticType + WasmNotSend, I: StaticType + WasmNotSend + 's> Node<'input, I> for DowncastBothNode<'n, 'input, 's, I, O> { type Output = DynFuture<'input, O>; #[inline] fn eval(&'input self, input: I) -> Self::Output { @@ -176,8 +186,8 @@ impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + Wasm self.node.serialize() } } -impl DowncastBothNode { - pub const fn new(node: SharedNodeContainer) -> Self { +impl<'n: 'i, 'i: 's, 's, I, O> DowncastBothNode<'n, 'i, 's, I, O> { + pub const fn new(node: SharedNodeContainer<'n, 'i, 's>) -> Self { Self { node, _i: core::marker::PhantomData, @@ -189,7 +199,7 @@ pub struct FutureWrapperNode { node: Node, } -impl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode +impl<'i, T: WasmNotSend, N> Node<'i, T> for FutureWrapperNode where N: Node<'i, T, Output: WasmNotSend> + WasmNotSend, { @@ -222,7 +232,7 @@ pub struct DynAnyNode { _o: PhantomData, } -impl<'input, _I: 'input + StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N> +impl<'input, _I: StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N> where N: Node<'input, _I, Output = DynFuture<'input, _O>>, { @@ -263,7 +273,7 @@ where self.node.serialize() } } -impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> DynAnyNode<_I, _O, N> +impl<'input, _I: StaticType, _O: 'input + StaticType, N: 'input> DynAnyNode<_I, _O, N> where N: Node<'input, _I, Output = DynFuture<'input, _O>>, { diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 8e2a2d7498..00430dd4cf 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -2,7 +2,8 @@ use crate::application_io::TextureFrame; use crate::raster::bbox::AxisAlignedBbox; use crate::raster::{ImageFrame, Pixel}; use crate::vector::VectorData; -use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup}; +use crate::Context; +use crate::{Artboard, ArtboardGroup, Color, ContextImpl, Ctx, ExtractFootprint, GraphicElement, GraphicGroup}; use glam::{DAffine2, DVec2}; @@ -177,7 +178,7 @@ impl From<()> for Footprint { } #[node_macro::node(category("Debug"))] -fn cull(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame, ArtboardGroup)] data: T) -> T { +fn cull(_: impl Ctx, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame, ArtboardGroup)] data: T) -> T { data } @@ -211,50 +212,119 @@ impl ApplyTransform for () { fn apply_transform(&mut self, &_modification: &DAffine2) {} } +// async fn transform2<'call, 'input: 'call, 'n: 'call, T: 'n + TransformMut, _Input: ExtractFootprint + 'call>( +// input: _Input, +// transform_target: impl crate::Node<'call, Context<'call>, Output = T> + 'n, +// ) -> T +// where +// { +// let footprint = input.footprint().unwrap(); +// let ctx: ContextImpl<'_> = ContextImpl { +// footprint: Some(&footprint), +// ..Default::default() +// }; +// let mut transform_target = transform_target.eval(Some(&ctx)); +// transform_target +// } + +// struct Context2<'a>(&'a str); + +// async fn transform2<'call, 'n: 'call, T: 'n>( +// // input: _Input, +// transform_target: &'n (impl for<'all_input> crate::Node<'all_input, Context2<'all_input>, Output: core::future::Future> + Sync), +// ) -> T { +// // // let footprint = *input.footprint().unwrap(); +// // let ctx: ContextImpl<'_> = ContextImpl { +// // // footprint: Some(&footprint), +// // ..Default::default() +// // }; +// // let transform_target = transform_target.eval(Some(&ctx)).await; +// // transform_target +// let string = String::from("test"); + +// transform_target.eval(Context2(&string.as_ref())).await +// } + +// async fn transform3<'call, 'n: 'call, T: 'n>(transform_target: impl for<'all_input> crate::Node<'all_input, Context2<'all_input>, Output = impl core::future::Future> + Sync) -> T { +// let string = String::from("test"); + +// transform_target.eval(Context2(&string.as_ref())).await +// } + +// // impl<'call, 'n: 'call, T: 'n, _Input: 'n, Node0> crate::Node<'n, _Input> for TransformNode +// // where +// // Node0: for<'all_input> crate::Node<'all_input, Context2<'all_input>, Output: core::future::Future>, +// // // for<'a, 'b, 'c> >>>::Output: crate::WasmNotSend, +// // Node0: Sync + Send, +// // { +// // type Output = core::pin::Pin + 'n>>; +// // #[inline] +// // fn eval(&'n self, __input: _Input) -> Self::Output { +// // let transform_target = &self.transform_target; +// // Box::pin(self::transform3(transform_target)) +// // } +// // } + +// // impl<'call, 'n: 'call, T: 'n, _Input: 'n + ExtractFootprint, Node0> crate::Node<'n, _Input> for TransformNode +// impl<'call, 'n: 'call, T: 'n, _Input: 'n, Node0, F0> crate::Node<'n, _Input> for TransformNode +// where +// Node0: for<'all_input> crate::Node<'all_input, Context2<'all_input>, Output = F0>, +// F0: core::future::Future + Send, +// // for<'a, 'b, 'c> >>>::Output: crate::WasmNotSend, +// Node0: Sync + Send, +// { +// // type Output = crate::registry::DynFuture<'n, T>; +// type Output = core::pin::Pin + 'n + Send>>; +// #[inline] +// fn eval(&'n self, __input: _Input) -> Self::Output { +// let transform_target = &self.transform_target; +// Box::pin(self::transform3(transform_target)) +// } +// } + +// pub struct TransformNode { +// pub(super) transform_target: Node0, +// } #[node_macro::node(category(""))] -async fn transform + 'n + ApplyTransform + Clone + Send + Sync, T: 'n + TransformMut>( - #[implementations( - (), - (), - (), - (), - Footprint, - )] - mut input: I, +async fn transform( + input: impl ExtractFootprint, #[implementations( - () -> VectorData, - () -> GraphicGroup, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> VectorData, - Footprint -> GraphicGroup, - Footprint -> ImageFrame, - Footprint -> TextureFrame, + Context -> VectorData, + Context -> GraphicGroup, + Context -> ImageFrame, + Context -> TextureFrame, )] - transform_target: impl Node, + transform_target: impl Node, Output = T>, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, _pivot: DVec2, -) -> T { +) -> T +where +{ let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]); - let footprint = input.clone().into(); + let mut footprint = *input.footprint().unwrap(); + if !footprint.ignore_modifications { - input.apply_transform(&modification); + footprint.apply_transform(&modification); } + let ctx: ContextImpl<'_> = ContextImpl { + footprint: Some(&footprint), + ..Default::default() + }; - let mut data = transform_target.eval(input).await; + let mut transform_target = transform_target.eval(Some(&ctx)).await; - let data_transform = data.transform_mut(); + let data_transform = transform_target.transform_mut(); *data_transform = modification * (*data_transform); - data + transform_target } #[node_macro::node(category(""))] fn replace_transform( - _: (), + _: impl Ctx, #[implementations(VectorData, ImageFrame, GraphicGroup)] mut data: Data, #[implementations(DAffine2)] transform: TransformInput, ) -> Data { diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 48389f02fb..047f55b55c 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -94,10 +94,10 @@ impl OnceCellNode { #[derive(Clone, Copy)] pub struct ClonedNode(pub T); -impl<'i, T: Clone + 'i> Node<'i, ()> for ClonedNode { +impl<'i, T: Clone + 'i, I> Node<'i, I> for ClonedNode { type Output = T; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { self.0.clone() } } diff --git a/node-graph/gcore/src/vector/mod.rs b/node-graph/gcore/src/vector/mod.rs index e729eede53..1aa72f4524 100644 --- a/node-graph/gcore/src/vector/mod.rs +++ b/node-graph/gcore/src/vector/mod.rs @@ -1,5 +1,5 @@ pub mod brush_stroke; -pub mod generator_nodes; +// pub mod generator_nodes; pub mod misc; pub mod style; @@ -8,7 +8,7 @@ pub use style::PathStyle; mod vector_data; pub use vector_data::*; -mod vector_nodes; -pub use vector_nodes::*; +// mod vector_nodes; +// pub use vector_nodes::*; pub use bezier_rs; diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index e41c998897..dd0081ff06 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -778,44 +778,44 @@ impl bezier_rs::Identifier for PointId { } } -impl crate::vector::ConcatElement for super::VectorData { - fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { - let new_ids = other - .point_domain - .id - .iter() - .filter(|id| self.point_domain.id.contains(id)) - .map(|&old| (old, old.generate_from_hash(node_id))); - let point_map = new_ids.collect::>(); - let new_ids = other - .segment_domain - .ids - .iter() - .filter(|id| self.segment_domain.ids.contains(id)) - .map(|&old| (old, old.generate_from_hash(node_id))); - let segment_map = new_ids.collect::>(); - let new_ids = other - .region_domain - .ids - .iter() - .filter(|id| self.region_domain.ids.contains(id)) - .map(|&old| (old, old.generate_from_hash(node_id))); - let region_map = new_ids.collect::>(); - let id_map = IdMap { - point_offset: self.point_domain.ids().len(), - point_map, - segment_map, - region_map, - }; - self.point_domain.concat(&other.point_domain, transform * other.transform, &id_map); - self.segment_domain.concat(&other.segment_domain, transform * other.transform, &id_map); - self.region_domain.concat(&other.region_domain, transform * other.transform, &id_map); - // TODO: properly deal with fills such as gradients - self.style = other.style.clone(); - self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied()); - self.alpha_blending = other.alpha_blending; - } -} +// impl crate::vector::ConcatElement for super::VectorData { +// fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { +// let new_ids = other +// .point_domain +// .id +// .iter() +// .filter(|id| self.point_domain.id.contains(id)) +// .map(|&old| (old, old.generate_from_hash(node_id))); +// let point_map = new_ids.collect::>(); +// let new_ids = other +// .segment_domain +// .ids +// .iter() +// .filter(|id| self.segment_domain.ids.contains(id)) +// .map(|&old| (old, old.generate_from_hash(node_id))); +// let segment_map = new_ids.collect::>(); +// let new_ids = other +// .region_domain +// .ids +// .iter() +// .filter(|id| self.region_domain.ids.contains(id)) +// .map(|&old| (old, old.generate_from_hash(node_id))); +// let region_map = new_ids.collect::>(); +// let id_map = IdMap { +// point_offset: self.point_domain.ids().len(), +// point_map, +// segment_map, +// region_map, +// }; +// self.point_domain.concat(&other.point_domain, transform * other.transform, &id_map); +// self.segment_domain.concat(&other.segment_domain, transform * other.transform, &id_map); +// self.region_domain.concat(&other.region_domain, transform * other.transform, &id_map); +// // TODO: properly deal with fills such as gradients +// self.style = other.style.clone(); +// self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied()); +// self.alpha_blending = other.alpha_blending; +// } +// } /// Represents the conversion of ids used when concatenating vector data with conflicting ids. struct IdMap { diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 2e7cecb6e5..d58092e49e 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -423,24 +423,24 @@ impl core::hash::Hash for VectorModification { use crate::transform::Footprint; /// A node that applies a procedural modification to some [`VectorData`]. -#[node_macro::node(category(""))] -async fn path_modify( - #[implementations( - (), - Footprint, - )] - input: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, - modification: Box, -) -> VectorData { - let mut vector_data = vector_data.eval(input).await; - modification.apply(&mut vector_data); - vector_data -} +// #[node_macro::node(category(""))] +// async fn path_modify( +// #[implementations( +// (), +// Footprint, +// )] +// input: F, +// #[implementations( +// () -> VectorData, +// Footprint -> VectorData, +// )] +// vector_data: impl Node, +// modification: Box, +// ) -> VectorData { +// let mut vector_data = vector_data.eval(input).await; +// modification.apply(&mut vector_data); +// vector_data +// } #[test] fn modify_new() { diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 9113641126..0de9158809 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -78,14 +78,16 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result = fields .iter() .map(|field| match field { ParsedField::Regular { ty, .. } => ty.clone(), ParsedField::Node { output_type, input_type, .. } => match parsed.is_async { - true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output: core::future::Future + #graphene_core::WasmNotSend>), + true => parse_quote!(impl for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = impl core::future::Future >), - false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>), + false => parse_quote!(impl for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = #output_type>), }, }) .collect(); @@ -138,7 +140,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let name = &pat_ident.ident; - quote! { let #name = self.#name.eval(()); } + quote! { let #name = self.#name.eval(__input); } + // quote! { let #name = self.#name.eval(()); } } ParsedField::Node { pat_ident, .. } => { let name = &pat_ident.ident; @@ -155,15 +158,31 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result quote!(#name: #graphene_core::Node<'n, (), Output = #ty> ), + (ParsedField::Regular { ty, .. }, _) => quote!( + #ty: Send, + #name: 'n, + &'n #name: for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = #ty> + ), (ParsedField::Node { input_type, output_type, .. }, false) => { - quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output = #output_type> + #graphene_core::WasmNotSync) + quote!( + #name: 'n, + &'n #name: for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = #output_type> + #graphene_core::WasmNotSync + ) } (ParsedField::Node { input_type, output_type, .. }, true) => { - quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output: core::future::Future + #graphene_core::WasmNotSend> + #graphene_core::WasmNotSync) + let id = future_idents.len(); + let fut_ident = format_ident!("F{}", id); + future_idents.push(fut_ident.clone()); + + quote!( + #fut_ident: core::future::Future + #graphene_core::WasmNotSend + 'n, + #name: Send + Sync + 'n, + &'n #name: for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = #fut_ident > + #graphene_core::WasmNotSync + ) } }); } @@ -217,10 +236,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body + #async_keyword fn #fn_name <'call, 'n: 'call, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body #[automatically_derived] - impl<'n, #(#fn_generics,)* #(#struct_generics,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> + impl<'call, 'n: 'call, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> #struct_where_clause { #eval_impl @@ -292,7 +311,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st } let mut constructors = Vec::new(); - let unit = parse_quote!(()); + let unit = parse_quote!(gcore::Context); let parameter_types: Vec<_> = parsed .fields .iter() @@ -349,7 +368,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st } else { quote!( #downcast_node - let #field_name = #field_name.eval(()).await; + let #field_name = #field_name.eval(None).await; let #field_name = ClonedNode::new(#field_name); let #field_name: TypeNode<_, #input_type, #output_type> = TypeNode::new(#field_name); // try polling futures @@ -375,7 +394,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st // try polling futures #future_node let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node); - Box::new(any) as TypeErasedBox<'_> + Box::new(any) as TypeErasedBox<'_, '_, '_> }) }, { let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*); diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index b3d0d0da73..d28a8c0d86 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -7,7 +7,8 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::{Comma, RArrow}; use syn::{ - parse_quote, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, WhereClause, + parse_quote, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lifetime, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, + WhereClause, }; use crate::codegen::generate_node_code; @@ -507,7 +508,8 @@ impl ParsedNodeFn { fn replace_impl_trait_in_input(&mut self) { if let Type::ImplTrait(impl_trait) = self.input.ty.clone() { let ident = Ident::new("_Input", impl_trait.span()); - let bounds = impl_trait.bounds; + let mut bounds = impl_trait.bounds; + bounds.push(parse_quote!('n)); self.fn_generics.push(GenericParam::Type(TypeParam { attrs: Default::default(), ident: ident.clone(), @@ -521,6 +523,9 @@ impl ParsedNodeFn { self.input.implementations.push(parse_quote!(gcore::Context)); } } + if self.input.pat_ident.ident == "_" { + self.input.pat_ident.ident = Ident::new("__ctx", self.input.pat_ident.ident.span()); + } } } From 597e7297ca092ff64f79adec49560349ac3727ec Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 27 Jan 2025 12:29:21 +0100 Subject: [PATCH 04/34] Return Any instead of DynAny --- node-graph/gcore/src/context.rs | 251 +++++++++++++++++++-------- node-graph/gcore/src/registry.rs | 64 +++---- node-graph/gcore/src/transform.rs | 12 +- node-graph/node-macro/src/codegen.rs | 25 ++- node-graph/wgpu-executor/src/lib.rs | 36 ++-- 5 files changed, 243 insertions(+), 145 deletions(-) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 0d456b7215..7ee92ef296 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -1,64 +1,109 @@ -use core::borrow::Borrow; +use core::{ + any::Any, + borrow::Borrow, + ops::{Deref, Index}, +}; +use std::sync::Arc; use crate::transform::Footprint; use dyn_any::DynAny; -pub trait Ctx: Copy + Send {} +pub trait Ctx: Clone + Send {} -pub trait ExtractFootprint: Ctx { +pub trait ExtractFootprint { fn footprint(&self) -> Option<&Footprint>; } -pub trait ExtractTime: Ctx { +pub trait ExtractTime { fn time(&self) -> Option; } -pub trait ExtractIndex: Ctx { +pub trait ExtractIndex { fn index(&self) -> Option; } -pub trait ExtractVarArgs: Ctx { +// Consider returning a slice or something like that +pub trait ExtractVarArgs { // Call this lifetime 'b so it is less likely to coflict when auto generating the function signature for implementation - fn vararg<'b>(&'b self, index: usize) -> Result + Send + Sync, VarArgsResult> - where - Self: 'b; + fn vararg(&self, index: usize) -> Result, VarArgsResult>; + fn varargs_len(&self) -> Result; +} +// Consider returning a slice or something like that +pub trait CloneVarArgs: ExtractVarArgs { + // fn box_clone(&self) -> Vec; + fn box_clone(&self) -> Box; } +pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs {} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum VarArgsResult { IndexOutOfBounds, NoVarArgs, } -impl Ctx for Option<&T> {} +impl Ctx for Option {} +impl Ctx for &T {} impl Ctx for () {} impl Ctx for Footprint {} +impl ExtractFootprint for () { + fn footprint(&self) -> Option<&Footprint> { + None + } +} -impl ExtractFootprint for Option<&T> { +impl<'n, T: ExtractFootprint + Ctx + Sync + Send> ExtractFootprint for &'n T { + fn footprint(&self) -> Option<&Footprint> { + (*self).footprint() + } +} +impl<'n, T: ExtractFootprint> ExtractFootprint for Arc +where + Arc: Ctx, +{ fn footprint(&self) -> Option<&Footprint> { - self.and_then(|x| x.footprint()) + (**self).footprint() } } -impl ExtractTime for Option<&T> { + +impl ExtractFootprint for Option { + fn footprint(&self) -> Option<&Footprint> { + self.as_ref().and_then(|x| x.footprint()) + } +} +impl ExtractTime for Option { fn time(&self) -> Option { - self.and_then(|x| x.time()) + self.as_ref().and_then(|x| x.time()) } } -impl ExtractIndex for Option<&T> { +impl ExtractIndex for Option { fn index(&self) -> Option { - self.and_then(|x| x.index()) + self.as_ref().and_then(|x| x.index()) } } -impl ExtractVarArgs for Option<&T> { - fn vararg<'b>(&'b self, index: usize) -> Result, VarArgsResult> - where - Self: 'b, - { - let Some(inner) = self else { return Err(VarArgsResult::NoVarArgs) }; +impl ExtractVarArgs for Option { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + let Some(ref inner) = self else { return Err(VarArgsResult::NoVarArgs) }; inner.vararg(index) } + + fn varargs_len(&self) -> Result { + let Some(ref inner) = self else { return Err(VarArgsResult::NoVarArgs) }; + inner.varargs_len() + } +} + +impl<'a, T: ExtractVarArgs + Sync> ExtractVarArgs for &'a T { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + (*self).vararg(index) + } + + fn varargs_len(&self) -> Result { + (*self).varargs_len() + } } impl Ctx for ContextImpl<'_> {} +impl Ctx for Arc {} impl ExtractFootprint for ContextImpl<'_> { fn footprint(&self) -> Option<&Footprint> { @@ -75,18 +120,74 @@ impl ExtractIndex for ContextImpl<'_> { self.index } } -impl ExtractVarArgs for ContextImpl<'_> { - fn vararg<'b>(&'b self, index: usize) -> Result + Send + Sync, VarArgsResult> - where - Self: 'b, - { +impl<'a> ExtractVarArgs for ContextImpl<'a> { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; + inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied() + } + + fn varargs_len(&self) -> Result { let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; - inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds) + Ok(inner.len()) } } -pub type Context<'a> = Option<&'a ContextImpl<'a>>; -type DynRef<'a> = &'a (dyn DynAny<'a> + 'a + Send + Sync); +impl ExtractFootprint for Arc { + fn footprint(&self) -> Option<&Footprint> { + self.footprint.as_ref() + } +} +impl ExtractTime for Arc { + fn time(&self) -> Option { + self.time + } +} +impl ExtractIndex for Arc { + fn index(&self) -> Option { + self.index + } +} +impl ExtractVarArgs for Arc { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + let Some(ref inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; + inner.get(index).map(|x| x.as_ref()).ok_or(VarArgsResult::IndexOutOfBounds) + } + + fn varargs_len(&self) -> Result { + let Some(ref inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; + Ok(inner.len()) + } +} + +pub type Context<'a> = Option>; +type DynRef<'a> = &'a (dyn Any + Send + Sync); +type DynBox = Box; + +#[derive(Default, dyn_any::DynAny)] +pub struct OwnedContextImpl { + pub footprint: Option, + pub(crate) varargs: Option>, + pub(crate) parent: Option>, + // This could be converted into a single enum to save extra bytes + pub(crate) index: Option, + pub(crate) time: Option, +} + +impl From for OwnedContextImpl { + fn from(value: T) -> Self { + let footprint = value.footprint().copied(); + let index = value.index(); + let time = value.time(); + let parent = value.box_clone(); + OwnedContextImpl { + footprint, + varargs: None, + parent: Some(parent.into()), + index, + time, + } + } +} #[derive(Default, Clone, Copy, dyn_any::DynAny)] pub struct ContextImpl<'a> { @@ -108,51 +209,51 @@ impl<'a> ContextImpl<'a> { ..*self } } - #[cfg(feature = "alloc")] - pub fn reborrow_var_args_to_vec<'short>(&self) -> Option]>> - where - 'a: 'short, - { - self.varargs.map(|x| shorten_lifetime_to_vec(x).into()) - } - pub fn reborrow_var_args_to_buffer<'short, const N: usize>(&self, buffer: &'short mut [DynRef<'short>; N]) -> Option<&'short [DynRef<'short>]> - where - 'a: 'short, - { - self.varargs.map(|x| shorten_lifetime_to_buffer(x, buffer)) - } + // #[cfg(feature = "alloc")] + // pub fn reborrow_var_args_to_vec<'short>(&self) -> Option]>> + // where + // 'a: 'short, + // { + // self.varargs.map(|x| shorten_lifetime_to_vec(x).into()) + // } + // pub fn reborrow_var_args_to_buffer<'short, const N: usize>(&self, buffer: &'short mut [DynRef<'short>; N]) -> Option<&'short [DynRef<'short>]> + // where + // 'a: 'short, + // { + // self.varargs.map(|x| shorten_lifetime_to_buffer(x, buffer)) + // } } -fn shorten_lifetime_to_vec<'c, 'b: 'c>(input: &'b [DynRef<'b>]) -> Vec> { - input.iter().map(|&x| x.reborrow_ref()).collect() -} -fn shorten_lifetime_to_buffer<'c, 'b: 'c, const N: usize>(input: &'b [DynRef<'b>], buffer: &'c mut [DynRef<'c>; N]) -> &'c [DynRef<'c>] { - let iter = input.iter().map(|&x| x.reborrow_ref()).zip(buffer.iter_mut()); - if input.len() > N { - unreachable!("Insufficient buffer size for varargs"); - } - for (data, buffer_slot) in iter { - *buffer_slot = data.reborrow_ref(); - } - &buffer[..input.len()] -} +// fn shorten_lifetime_to_vec<'c, 'b: 'c>(input: &'b [DynRef<'b>]) -> Vec> { +// input.iter().map(|&x| x.reborrow_ref()).collect() +// } +// fn shorten_lifetime_to_buffer<'c, 'b: 'c, const N: usize>(input: &'b [DynRef<'b>], buffer: &'c mut [DynRef<'c>; N]) -> &'c [DynRef<'c>] { +// let iter = input.iter().map(|&x| x.reborrow_ref()).zip(buffer.iter_mut()); +// if input.len() > N { +// unreachable!("Insufficient buffer size for varargs"); +// } +// for (data, buffer_slot) in iter { +// *buffer_slot = data.reborrow_ref(); +// } +// &buffer[..input.len()] +// } -#[test] -fn shorten_lifetime_compile_test() { - let context: ContextImpl<'static> = const { - ContextImpl { - footprint: None, - varargs: None, - index: None, - time: None, - } - }; - let footprint = Footprint::default(); - let local_varargs = context.reborrow_var_args_to_vec(); - let out = context.with_footprint(&footprint, local_varargs.as_ref()); - assert!(out.footprint().is_some()); - let mut buffer: [_; 0] = []; - let local_varargs_buf = context.reborrow_var_args_to_buffer(&mut buffer); - let out = context.with_footprint(&footprint, local_varargs_buf.as_ref()); - assert!(out.footprint().is_some()); -} +// #[test] +// fn shorten_lifetime_compile_test() { +// let context: ContextImpl<'static> = const { +// ContextImpl { +// footprint: None, +// varargs: None, +// index: None, +// time: None, +// } +// }; +// let footprint = Footprint::default(); +// let local_varargs = context.reborrow_var_args_to_vec(); +// let out = context.with_footprint(&footprint, local_varargs.as_ref()); +// assert!(out.footprint().is_some()); +// let mut buffer: [_; 0] = []; +// let local_varargs_buf = context.reborrow_var_args_to_buffer(&mut buffer); +// let out = context.with_footprint(&footprint, local_varargs_buf.as_ref()); +// assert!(out.footprint().is_some()); +// } diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index d5c76f2cdb..3d1868d8e2 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -73,28 +73,28 @@ pub type Any<'n> = Box + 'n>; pub type FutureAny<'n> = DynFuture<'n, Any<'n>>; // TODO: is this safe? This is assumed to be send+sync. #[cfg(not(target_arch = "wasm32"))] -pub type TypeErasedNode<'n, 'i, 's> = dyn NodeIO<'i, Any<'s>, Output = FutureAny<'i>> + 'n + Send + Sync; +pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync; #[cfg(target_arch = "wasm32")] -pub type TypeErasedNode<'n, 'i, 's> = dyn NodeIO<'i, Any<'s>, Output = FutureAny<'i>> + 'n; -pub type TypeErasedPinnedRef<'n, 'i, 's> = Pin<&'n TypeErasedNode<'n, 'i, 's>>; -pub type TypeErasedRef<'n, 'i, 's> = &'n TypeErasedNode<'n, 'i, 's>; -pub type TypeErasedBox<'n, 'i, 's> = Box>; -pub type TypeErasedPinned<'n, 'i, 's> = Pin>>; +pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n; +pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>; +pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>; +pub type TypeErasedBox<'n> = Box>; +pub type TypeErasedPinned<'n> = Pin>>; -pub type SharedNodeContainer<'n, 'i, 's> = std::sync::Arc>; +pub type SharedNodeContainer = std::sync::Arc; -pub type NodeConstructor = for<'n, 'i, 's> fn(Vec>) -> DynFuture<'n, TypeErasedBox<'n, 'i, 's>>; +pub type NodeConstructor = fn(Vec) -> DynFuture<'static, TypeErasedBox<'static>>; #[derive(Clone)] -pub struct NodeContainer<'n, 'i, 's> { +pub struct NodeContainer { #[cfg(feature = "dealloc_nodes")] - pub node: *const TypeErasedNode<'n, 'i, 's>, + pub node: *const TypeErasedNode<'static>, #[cfg(not(feature = "dealloc_nodes"))] - pub node: TypeErasedRef<'n, 'i, 's>, + pub node: TypeErasedRef<'static>, } -impl<'n: 'i, 'i: 's, 's> Deref for NodeContainer<'n, 'i, 's> { - type Target = TypeErasedNode<'n, 'i, 's>; +impl Deref for NodeContainer { + type Target = TypeErasedNode<'static>; #[cfg(feature = "dealloc_nodes")] fn deref(&self) -> &Self::Target { @@ -112,45 +112,35 @@ impl<'n: 'i, 'i: 's, 's> Deref for NodeContainer<'n, 'i, 's> { /// Marks NodeContainer as Sync. This dissallows the use of threadlocal storage for nodes as this would invalidate references to them. // TODO: implement this on a higher level wrapper to avoid missuse #[cfg(feature = "dealloc_nodes")] -unsafe impl<'n: 'i, 'i, 's> Send for NodeContainer<'n, 'i, 's> {} +unsafe impl Send for NodeContainer {} #[cfg(feature = "dealloc_nodes")] -unsafe impl<'n: 'i, 'i, 's> Sync for NodeContainer<'n, 'i, 's> {} +unsafe impl Sync for NodeContainer {} #[cfg(feature = "dealloc_nodes")] -impl<'n, 'i, 's> Drop for NodeContainer<'n, 'i, 's> { +impl Drop for NodeContainer { fn drop(&mut self) { unsafe { self.dealloc_unchecked() } } } -impl<'n: 'i, 'i: 's, 's> core::fmt::Debug for NodeContainer<'n, 'i, 's> { +impl core::fmt::Debug for NodeContainer { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("NodeContainer").finish() } } -impl<'n: 'i, 'i: 's, 's> NodeContainer<'n, 'i, 's> { - pub fn new(node: TypeErasedBox<'n, 'i, 's>) -> SharedNodeContainer<'n, 'i, 's> { +impl NodeContainer { + pub fn new(node: TypeErasedBox<'static>) -> SharedNodeContainer { let node = Box::leak(node); Self { node }.into() } -} -impl<'n, 'i, 's> NodeContainer<'n, 'i, 's> { #[cfg(feature = "dealloc_nodes")] unsafe fn dealloc_unchecked(&mut self) { std::mem::drop(Box::from_raw(self.node as *mut TypeErasedNode)); } } -impl<'n: 'i, 'i: 's, 's> Node<'i, Any<'s>> for SharedNodeContainer<'n, 'i, 's> { - type Output = FutureAny<'i>; - - fn eval(&'i self, input: Any<'s>) -> Self::Output { - (**self).eval(input) - } -} - use crate::Node; use crate::WasmNotSend; use dyn_any::StaticType; @@ -159,12 +149,12 @@ use std::marker::PhantomData; /// Boxes the input and downcasts the output. /// Wraps around a node taking Box and returning Box #[derive(Clone)] -pub struct DowncastBothNode<'n, 'i, 's, I, O> { - node: SharedNodeContainer<'n, 'i, 's>, +pub struct DowncastBothNode { + node: SharedNodeContainer, _i: PhantomData, _o: PhantomData, } -impl<'input: 's, 'n: 'input, 's, O: 'input + StaticType + WasmNotSend, I: StaticType + WasmNotSend + 's> Node<'input, I> for DowncastBothNode<'n, 'input, 's, I, O> { +impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode { type Output = DynFuture<'input, O>; #[inline] fn eval(&'input self, input: I) -> Self::Output { @@ -186,8 +176,8 @@ impl<'input: 's, 'n: 'input, 's, O: 'input + StaticType + WasmNotSend, I: Static self.node.serialize() } } -impl<'n: 'i, 'i: 's, 's, I, O> DowncastBothNode<'n, 'i, 's, I, O> { - pub const fn new(node: SharedNodeContainer<'n, 'i, 's>) -> Self { +impl DowncastBothNode { + pub const fn new(node: SharedNodeContainer) -> Self { Self { node, _i: core::marker::PhantomData, @@ -199,7 +189,7 @@ pub struct FutureWrapperNode { node: Node, } -impl<'i, T: WasmNotSend, N> Node<'i, T> for FutureWrapperNode +impl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode where N: Node<'i, T, Output: WasmNotSend> + WasmNotSend, { @@ -232,7 +222,7 @@ pub struct DynAnyNode { _o: PhantomData, } -impl<'input, _I: StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N> +impl<'input, _I: 'input + StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N> where N: Node<'input, _I, Output = DynFuture<'input, _O>>, { @@ -273,7 +263,7 @@ where self.node.serialize() } } -impl<'input, _I: StaticType, _O: 'input + StaticType, N: 'input> DynAnyNode<_I, _O, N> +impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> DynAnyNode<_I, _O, N> where N: Node<'input, _I, Output = DynFuture<'input, _O>>, { diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 00430dd4cf..bc086dc69d 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -2,8 +2,8 @@ use crate::application_io::TextureFrame; use crate::raster::bbox::AxisAlignedBbox; use crate::raster::{ImageFrame, Pixel}; use crate::vector::VectorData; -use crate::Context; use crate::{Artboard, ArtboardGroup, Color, ContextImpl, Ctx, ExtractFootprint, GraphicElement, GraphicGroup}; +use crate::{Context, OwnedContextImpl}; use glam::{DAffine2, DVec2}; @@ -287,14 +287,14 @@ impl ApplyTransform for () { // } #[node_macro::node(category(""))] async fn transform( - input: impl ExtractFootprint, + input: impl ExtractFootprint + Ctx, #[implementations( Context -> VectorData, Context -> GraphicGroup, Context -> ImageFrame, Context -> TextureFrame, )] - transform_target: impl Node, Output = T>, + transform_target: impl Node, Output = T>, translate: DVec2, rotate: f64, scale: DVec2, @@ -309,12 +309,12 @@ where if !footprint.ignore_modifications { footprint.apply_transform(&modification); } - let ctx: ContextImpl<'_> = ContextImpl { - footprint: Some(&footprint), + let ctx = OwnedContextImpl { + footprint: Some(footprint), ..Default::default() }; - let mut transform_target = transform_target.eval(Some(&ctx)).await; + let mut transform_target = transform_target.eval(Some(ctx.into())).await; let data_transform = transform_target.transform_mut(); *data_transform = modification * (*data_transform); diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 0de9158809..4869573463 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -37,7 +37,6 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result = fields.iter().enumerate().map(|(i, _)| format_ident!("Node{}", i)).collect(); let input_ident = &input.pat_ident; - let input_type = &input.ty; let field_idents: Vec<_> = fields .iter() @@ -85,9 +84,9 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result ty.clone(), ParsedField::Node { output_type, input_type, .. } => match parsed.is_async { - true => parse_quote!(impl for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = impl core::future::Future >), + true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = impl core::future::Future > ), - false => parse_quote!(impl for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = #output_type>), + false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>), }, }) .collect(); @@ -140,7 +139,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let name = &pat_ident.ident; - quote! { let #name = self.#name.eval(__input); } + quote! { let #name = self.#name.eval(__input.clone()); } // quote! { let #name = self.#name.eval(()); } } ParsedField::Node { pat_ident, .. } => { @@ -164,13 +163,13 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result quote!( #ty: Send, - #name: 'n, - &'n #name: for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = #ty> + // #name: 'n, + #name: #graphene_core::Node<'n, #input_type, Output = #ty> ), (ParsedField::Node { input_type, output_type, .. }, false) => { quote!( - #name: 'n, - &'n #name: for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = #output_type> + #graphene_core::WasmNotSync + // #name: 'n, + #name: #graphene_core::Node<'n, #input_type, Output = #output_type> + #graphene_core::WasmNotSync ) } (ParsedField::Node { input_type, output_type, .. }, true) => { @@ -180,8 +179,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result + #graphene_core::WasmNotSend + 'n, - #name: Send + Sync + 'n, - &'n #name: for<'all_input> #graphene_core::Node<'all_input, #input_type, Output = #fut_ident > + #graphene_core::WasmNotSync + // #name: Send + Sync + 'n, + #name: #graphene_core::Node<'n, #input_type, Output = #fut_ident > + #graphene_core::WasmNotSync ) } }); @@ -236,10 +235,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body + #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body #[automatically_derived] - impl<'call, 'n: 'call, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> + impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> #struct_where_clause { #eval_impl @@ -394,7 +393,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st // try polling futures #future_node let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node); - Box::new(any) as TypeErasedBox<'_, '_, '_> + Box::new(any) as TypeErasedBox<'_> }) }, { let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*); diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index f283e62d0d..b6d8639542 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -9,8 +9,8 @@ use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, Tex use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle, TextureFrame}; use graphene_core::raster::{Image, ImageFrame, SRGBA8}; use graphene_core::transform::{Footprint, Transform}; -use graphene_core::Type; -use graphene_core::{Color, Cow, Node, SurfaceFrame}; +use graphene_core::{Color, Cow, ExtractFootprint, Node, OwnedContextImpl, SurfaceFrame}; +use graphene_core::{Ctx, Type}; use anyhow::{bail, Result}; use futures::Future; @@ -824,12 +824,12 @@ impl ShaderInputNode { } #[node_macro::node(category(""))] -async fn uniform<'a: 'n, T: ToUniformBuffer + Send + 'n>(_: (), #[implementations(f32, DAffine2)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { +async fn uniform<'a: 'n, T: ToUniformBuffer + Send + 'n>(_: impl Ctx, #[implementations(f32, DAffine2)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { executor.create_uniform_buffer(data).unwrap() } #[node_macro::node(category(""))] -async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: (), #[implementations(Vec)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { +async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: impl Ctx, #[implementations(Vec)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { executor .create_storage_buffer( data, @@ -844,18 +844,18 @@ async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: (), #[implementation } #[node_macro::node(category(""))] -async fn create_output_buffer<'a: 'n>(_: (), size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc { +async fn create_output_buffer<'a: 'n>(_: impl Ctx + 'a, size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc { Arc::new(executor.create_output_buffer(size, ty, true).unwrap()) } #[node_macro::node(skip_impl)] -async fn create_compute_pass<'a: 'n>(_: (), layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer { +async fn create_compute_pass<'a: 'n>(_: impl Ctx + 'a, layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer { executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap() } #[node_macro::node(category("Debug: GPU"))] async fn create_pipeline_layout( - _: (), + _: impl Ctx, shader: impl Node<(), Output = ShaderHandle>, entry_point: String, bind_group: impl Node<(), Output = Bindgroup>, @@ -870,14 +870,14 @@ async fn create_pipeline_layout( } #[node_macro::node(category(""))] -async fn read_output_buffer<'a: 'n>(_: (), buffer: Arc, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec { +async fn read_output_buffer<'a: 'n>(_: impl Ctx + 'a, buffer: Arc, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec { executor.read_output_buffer(buffer).await.unwrap() } pub type WindowHandle = Arc>; #[node_macro::node(skip_impl)] -fn create_gpu_surface<'a: 'n, Io: ApplicationIo + 'a + Send + Sync>(_: (), editor_api: &'a EditorApi) -> Option { +fn create_gpu_surface<'a: 'n, Io: ApplicationIo + 'a + Send + Sync>(_: impl Ctx + 'a, editor_api: &'a EditorApi) -> Option { let canvas = editor_api.application_io.as_ref()?.window()?; let executor = editor_api.application_io.as_ref()?.gpu_executor()?; Some(Arc::new(executor.create_surface(canvas).ok()?)) @@ -890,7 +890,13 @@ pub struct ShaderInputFrame { } #[node_macro::node(category(""))] -async fn render_texture<'a: 'n>(_: (), footprint: Footprint, image: impl Node, surface: Option, executor: &'a WgpuExecutor) -> SurfaceFrame { +async fn render_texture<'a: 'n>( + _: impl Ctx + 'a, + footprint: Footprint, + image: impl Node, + surface: Option, + executor: &'a WgpuExecutor, +) -> SurfaceFrame { let surface = surface.unwrap(); let surface_id = surface.window_id; let image = image.eval(footprint).await; @@ -906,13 +912,15 @@ async fn render_texture<'a: 'n>(_: (), footprint: Footprint, image: impl Node( - #[implementations((), Footprint)] footprint: F, - #[implementations(() -> ImageFrame, Footprint -> ImageFrame)] input: impl Node>, +async fn upload_texture<'a: 'n>( + footprint: impl ExtractFootprint + Ctx, + #[implementations(graphene_core::Context -> ImageFrame)] input: impl Node, Output = ImageFrame>, executor: &'a WgpuExecutor, ) -> TextureFrame { // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); - let input = input.eval(footprint).await; + let footprint = footprint.footprint().copied(); + let ctx = OwnedContextImpl { footprint, ..Default::default() }; + let input = input.eval(Some(ctx.into())).await; let new_data = input.image.data.into_iter().map(SRGBA8::from).collect(); let new_image = Image { width: input.image.width, From aac084a119a233e11ef491d30ac87b87e6703adb Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 27 Jan 2025 15:22:02 +0100 Subject: [PATCH 05/34] Fix send implementation for inputs with lifetimes --- node-graph/gcore/src/context.rs | 8 ++++---- node-graph/node-macro/src/codegen.rs | 23 +++++++++++++---------- node-graph/wgpu-executor/src/lib.rs | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 7ee92ef296..fa3cee23df 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -166,11 +166,11 @@ type DynBox = Box; #[derive(Default, dyn_any::DynAny)] pub struct OwnedContextImpl { pub footprint: Option, - pub(crate) varargs: Option>, - pub(crate) parent: Option>, + pub varargs: Option>, + pub parent: Option>, // This could be converted into a single enum to save extra bytes - pub(crate) index: Option, - pub(crate) time: Option, + pub index: Option, + pub time: Option, } impl From for OwnedContextImpl { diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 4869573463..804c358321 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -161,11 +161,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result quote!( - #ty: Send, - // #name: 'n, - #name: #graphene_core::Node<'n, #input_type, Output = #ty> - ), + (ParsedField::Regular { ty, .. }, _) => { + let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all"); + quote!( + for<'all> #all_lifetime_ty: Send, + // #name: 'n, + #name: #graphene_core::Node<'n, #input_type, Output = #ty> + ) + } (ParsedField::Node { input_type, output_type, .. }, false) => { quote!( // #name: 'n, @@ -337,7 +340,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st } } .into_iter() - .map(|(input, out, node)| (substitute_lifetimes(input.clone()), substitute_lifetimes(out.clone()), node)) + .map(|(input, out, node)| (substitute_lifetimes(input.clone(), "_"), substitute_lifetimes(out.clone(), "_"), node)) .collect::>() }) .collect(); @@ -430,11 +433,11 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st use syn::{visit_mut::VisitMut, GenericArgument, Lifetime, Type}; -struct LifetimeReplacer; +struct LifetimeReplacer(&'static str); impl VisitMut for LifetimeReplacer { fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) { - lifetime.ident = syn::Ident::new("_", lifetime.ident.span()); + lifetime.ident = syn::Ident::new(self.0, lifetime.ident.span()); } fn visit_type_mut(&mut self, ty: &mut Type) { @@ -459,7 +462,7 @@ impl VisitMut for LifetimeReplacer { } #[must_use] -fn substitute_lifetimes(mut ty: Type) -> Type { - LifetimeReplacer.visit_type_mut(&mut ty); +fn substitute_lifetimes(mut ty: Type, lifetime: &'static str) -> Type { + LifetimeReplacer(lifetime).visit_type_mut(&mut ty); ty } diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index b6d8639542..f89c776f35 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -914,7 +914,7 @@ async fn render_texture<'a: 'n>( #[node_macro::node(category(""))] async fn upload_texture<'a: 'n>( footprint: impl ExtractFootprint + Ctx, - #[implementations(graphene_core::Context -> ImageFrame)] input: impl Node, Output = ImageFrame>, + #[implementations(graphene_core::Context -> ImageFrame)] input: impl Node, Output = ImageFrame>, executor: &'a WgpuExecutor, ) -> TextureFrame { // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); From 183eaa5f323b27a01f898b3e05449751eb7e2c5c Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 28 Jan 2025 01:57:35 +0100 Subject: [PATCH 06/34] Port more nodes --- node-graph/gcore/src/context.rs | 142 ++-- node-graph/gcore/src/ops.rs | 985 +++++++++++++-------------- node-graph/gcore/src/transform.rs | 85 +-- node-graph/gstd/src/raster.rs | 13 +- node-graph/node-macro/src/parsing.rs | 3 +- node-graph/wgpu-executor/src/lib.rs | 2 +- 6 files changed, 602 insertions(+), 628 deletions(-) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index fa3cee23df..960764cd5b 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -1,25 +1,23 @@ -use core::{ - any::Any, - borrow::Borrow, - ops::{Deref, Index}, -}; +use core::{any::Any, borrow::Borrow}; use std::sync::Arc; use crate::transform::Footprint; -use dyn_any::DynAny; pub trait Ctx: Clone + Send {} pub trait ExtractFootprint { - fn footprint(&self) -> Option<&Footprint>; + fn try_footprint(&self) -> Option<&Footprint>; + fn footprint(&self) -> &Footprint { + self.try_footprint().expect("Context did not have a footprint") + } } pub trait ExtractTime { - fn time(&self) -> Option; + fn try_time(&self) -> Option; } pub trait ExtractIndex { - fn index(&self) -> Option; + fn try_index(&self) -> Option; } // Consider returning a slice or something like that @@ -31,11 +29,13 @@ pub trait ExtractVarArgs { // Consider returning a slice or something like that pub trait CloneVarArgs: ExtractVarArgs { // fn box_clone(&self) -> Vec; - fn box_clone(&self) -> Box; + fn arc_clone(&self) -> Option>; } pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs {} +impl ExtractAll for T {} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum VarArgsResult { IndexOutOfBounds, @@ -46,38 +46,30 @@ impl Ctx for &T {} impl Ctx for () {} impl Ctx for Footprint {} impl ExtractFootprint for () { - fn footprint(&self) -> Option<&Footprint> { + fn try_footprint(&self) -> Option<&Footprint> { None } } impl<'n, T: ExtractFootprint + Ctx + Sync + Send> ExtractFootprint for &'n T { - fn footprint(&self) -> Option<&Footprint> { - (*self).footprint() - } -} -impl<'n, T: ExtractFootprint> ExtractFootprint for Arc -where - Arc: Ctx, -{ - fn footprint(&self) -> Option<&Footprint> { - (**self).footprint() + fn try_footprint(&self) -> Option<&Footprint> { + (*self).try_footprint() } } impl ExtractFootprint for Option { - fn footprint(&self) -> Option<&Footprint> { - self.as_ref().and_then(|x| x.footprint()) + fn try_footprint(&self) -> Option<&Footprint> { + self.as_ref().and_then(|x| x.try_footprint()) } } impl ExtractTime for Option { - fn time(&self) -> Option { - self.as_ref().and_then(|x| x.time()) + fn try_time(&self) -> Option { + self.as_ref().and_then(|x| x.try_time()) } } impl ExtractIndex for Option { - fn index(&self) -> Option { - self.as_ref().and_then(|x| x.index()) + fn try_index(&self) -> Option { + self.as_ref().and_then(|x| x.try_index()) } } impl ExtractVarArgs for Option { @@ -91,8 +83,37 @@ impl ExtractVarArgs for Option { inner.varargs_len() } } +impl ExtractFootprint for Arc { + fn try_footprint(&self) -> Option<&Footprint> { + (**self).try_footprint() + } +} +impl ExtractTime for Arc { + fn try_time(&self) -> Option { + (**self).try_time() + } +} +impl ExtractIndex for Arc { + fn try_index(&self) -> Option { + (**self).try_index() + } +} +impl ExtractVarArgs for Arc { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + (**self).vararg(index) + } + + fn varargs_len(&self) -> Result { + (**self).varargs_len() + } +} +impl CloneVarArgs for Option { + fn arc_clone(&self) -> Option> { + self.as_ref().and_then(CloneVarArgs::arc_clone) + } +} -impl<'a, T: ExtractVarArgs + Sync> ExtractVarArgs for &'a T { +impl ExtractVarArgs for &T { fn vararg(&self, index: usize) -> Result, VarArgsResult> { (*self).vararg(index) } @@ -101,22 +122,27 @@ impl<'a, T: ExtractVarArgs + Sync> ExtractVarArgs for &'a T { (*self).varargs_len() } } +impl CloneVarArgs for Arc { + fn arc_clone(&self) -> Option> { + (**self).arc_clone() + } +} impl Ctx for ContextImpl<'_> {} impl Ctx for Arc {} impl ExtractFootprint for ContextImpl<'_> { - fn footprint(&self) -> Option<&Footprint> { + fn try_footprint(&self) -> Option<&Footprint> { self.footprint } } impl ExtractTime for ContextImpl<'_> { - fn time(&self) -> Option { + fn try_time(&self) -> Option { self.time } } impl ExtractIndex for ContextImpl<'_> { - fn index(&self) -> Option { + fn try_index(&self) -> Option { self.index } } @@ -132,33 +158,49 @@ impl<'a> ExtractVarArgs for ContextImpl<'a> { } } -impl ExtractFootprint for Arc { - fn footprint(&self) -> Option<&Footprint> { +impl ExtractFootprint for OwnedContextImpl { + fn try_footprint(&self) -> Option<&Footprint> { self.footprint.as_ref() } } -impl ExtractTime for Arc { - fn time(&self) -> Option { +impl ExtractTime for OwnedContextImpl { + fn try_time(&self) -> Option { self.time } } -impl ExtractIndex for Arc { - fn index(&self) -> Option { +impl ExtractIndex for OwnedContextImpl { + fn try_index(&self) -> Option { self.index } } -impl ExtractVarArgs for Arc { +impl ExtractVarArgs for OwnedContextImpl { fn vararg(&self, index: usize) -> Result, VarArgsResult> { - let Some(ref inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; + let Some(ref inner) = self.varargs else { + let Some(ref parent) = self.parent else { + return Err(VarArgsResult::NoVarArgs); + }; + return parent.vararg(index); + }; inner.get(index).map(|x| x.as_ref()).ok_or(VarArgsResult::IndexOutOfBounds) } fn varargs_len(&self) -> Result { - let Some(ref inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; + let Some(ref inner) = self.varargs else { + let Some(ref parent) = self.parent else { + return Err(VarArgsResult::NoVarArgs); + }; + return parent.varargs_len(); + }; Ok(inner.len()) } } +impl CloneVarArgs for OwnedContextImpl { + fn arc_clone(&self) -> Option> { + todo!() + } +} + pub type Context<'a> = Option>; type DynRef<'a> = &'a (dyn Any + Send + Sync); type DynBox = Box; @@ -173,22 +215,28 @@ pub struct OwnedContextImpl { pub time: Option, } -impl From for OwnedContextImpl { - fn from(value: T) -> Self { - let footprint = value.footprint().copied(); - let index = value.index(); - let time = value.time(); - let parent = value.box_clone(); +impl OwnedContextImpl { + pub fn from(value: T) -> Self { + let footprint = value.try_footprint().copied(); + let index = value.try_index(); + let time = value.try_time(); + let parent = value.arc_clone(); OwnedContextImpl { footprint, varargs: None, - parent: Some(parent.into()), + parent, index, time, } } } +impl OwnedContextImpl { + pub fn set_footprint(&mut self, footprint: Footprint) { + self.footprint = Some(footprint); + } +} + #[derive(Default, Clone, Copy, dyn_any::DynAny)] pub struct ContextImpl<'a> { pub(crate) footprint: Option<&'a crate::transform::Footprint>, diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 026d2aac0f..0b1acf0154 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -1,10 +1,9 @@ -// use crate::raster::BlendMode; -// use crate::raster::ImageFrame; +use crate::raster::BlendMode; +use crate::raster::ImageFrame; use crate::registry::types::Percentage; -// use crate::vector::style::GradientStops; +use crate::vector::style::GradientStops; use crate::Ctx; -// use crate::{Color, Node}; -use crate::Node; +use crate::{Color, Node}; use math_parser::ast; use math_parser::context::{EvalContext, NothingMap, ValueProvider}; @@ -38,50 +37,50 @@ impl ValueProvider for MathNodeContext { } } -// /// Calculates a mathematical expression with input values "A" and "B" -// #[node_macro::node(category("Math"))] -// fn math( -// _: impl Ctx, -// /// The value of "A" when calculating the expression -// #[implementations(f64, f32)] -// operand_a: U, -// /// A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2" -// #[default(A + B)] -// expression: String, -// /// The value of "B" when calculating the expression -// #[implementations(f64, f32)] -// #[default(1.)] -// operand_b: U, -// ) -> U { -// let (node, _unit) = match ast::Node::try_parse_from_str(&expression) { -// Ok(expr) => expr, -// Err(e) => { -// warn!("Invalid expression: `{expression}`\n{e:?}"); -// return U::from(0.).unwrap(); -// } -// }; -// let context = EvalContext::new( -// MathNodeContext { -// a: operand_a.to_f64().unwrap(), -// b: operand_b.to_f64().unwrap(), -// }, -// NothingMap, -// ); - -// let value = match node.eval(&context) { -// Ok(value) => value, -// Err(e) => { -// warn!("Expression evaluation error: {e:?}"); -// return U::from(0.).unwrap(); -// } -// }; - -// let Value::Number(num) = value; -// match num { -// Number::Real(val) => U::from(val).unwrap(), -// Number::Complex(c) => U::from(c.re).unwrap(), -// } -// } +/// Calculates a mathematical expression with input values "A" and "B" +#[node_macro::node(category("Math"))] +fn math( + _: impl Ctx, + /// The value of "A" when calculating the expression + #[implementations(f64, f32)] + operand_a: U, + /// A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2" + #[default(A + B)] + expression: String, + /// The value of "B" when calculating the expression + #[implementations(f64, f32)] + #[default(1.)] + operand_b: U, +) -> U { + let (node, _unit) = match ast::Node::try_parse_from_str(&expression) { + Ok(expr) => expr, + Err(e) => { + warn!("Invalid expression: `{expression}`\n{e:?}"); + return U::from(0.).unwrap(); + } + }; + let context = EvalContext::new( + MathNodeContext { + a: operand_a.to_f64().unwrap(), + b: operand_b.to_f64().unwrap(), + }, + NothingMap, + ); + + let value = match node.eval(&context) { + Ok(value) => value, + Err(e) => { + warn!("Expression evaluation error: {e:?}"); + return U::from(0.).unwrap(); + } + }; + + let Value::Number(num) = value; + match num { + Number::Real(val) => U::from(val).unwrap(), + Number::Complex(c) => U::from(c.re).unwrap(), + } +} /// The addition operation (+) calculates the sum of two numbers. #[node_macro::node(category("Math: Arithmetic"))] @@ -93,402 +92,402 @@ fn add, T>( augend + addend } -// /// The subtraction operation (-) calculates the difference between two numbers. -// #[node_macro::node(category("Math: Arithmetic"))] -// fn subtract, T>( -// _: impl Ctx, -// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U, -// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T, -// ) -> >::Output { -// minuend - subtrahend -// } - -// /// The multiplication operation (×) calculates the product of two numbers. -// #[node_macro::node(category("Math: Arithmetic"))] -// fn multiply, T>( -// _: impl Ctx, -// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U, -// #[default(1.)] -// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] -// multiplicand: T, -// ) -> >::Output { -// multiplier * multiplicand -// } - -// /// The division operation (÷) calculates the quotient of two numbers. -// #[node_macro::node(category("Math: Arithmetic"))] -// fn divide, T>( -// _: impl Ctx, -// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, -// #[default(1.)] -// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] -// denominator: T, -// ) -> >::Output { -// numerator / denominator -// } - -// /// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled. -// #[node_macro::node(category("Math: Arithmetic"))] -// fn modulo>>, T: Copy>( -// _: impl Ctx, -// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, -// #[default(2.)] -// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] -// modulus: T, -// always_positive: bool, -// ) -> >::Output { -// if always_positive { -// (numerator % modulus + modulus) % modulus -// } else { -// numerator % modulus -// } -// } - -// /// The exponent operation (^) calculates the result of raising a number to a power. -// #[node_macro::node(category("Math: Arithmetic"))] -// fn exponent, T>( -// _: impl Ctx, -// #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U, -// #[default(2.)] -// #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)] -// power: T, -// ) -> >::Output { -// base.pow(power) -// } - -// /// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n. -// #[node_macro::node(category("Math: Arithmetic"))] -// fn root( -// _: impl Ctx, -// #[default(2.)] -// #[implementations(f64, f32)] -// radicand: U, -// #[default(2.)] -// #[implementations(f64, f32)] -// degree: U, -// ) -> U { -// if degree == U::from(2.).unwrap() { -// radicand.sqrt() -// } else if degree == U::from(3.).unwrap() { -// radicand.cbrt() -// } else { -// radicand.powf(U::from(1.).unwrap() / degree) -// } -// } - -// /// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e". -// #[node_macro::node(category("Math: Arithmetic"))] -// fn logarithm( -// _: impl Ctx, -// #[implementations(f64, f32)] value: U, -// #[default(2.)] -// #[implementations(f64, f32)] -// base: U, -// ) -> U { -// if base == U::from(2.).unwrap() { -// value.log2() -// } else if base == U::from(10.).unwrap() { -// value.log10() -// } else if base - U::from(std::f64::consts::E).unwrap() < U::epsilon() * U::from(1e6).unwrap() { -// value.ln() -// } else { -// value.log(base) -// } -// } - -// /// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length. -// #[node_macro::node(category("Math: Trig"))] -// fn sine(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { -// if radians { -// theta.sin() -// } else { -// theta.to_radians().sin() -// } -// } - -// /// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length. -// #[node_macro::node(category("Math: Trig"))] -// fn cosine(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { -// if radians { -// theta.cos() -// } else { -// theta.to_radians().cos() -// } -// } - -// /// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length. -// #[node_macro::node(category("Math: Trig"))] -// fn tangent(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { -// if radians { -// theta.tan() -// } else { -// theta.to_radians().tan() -// } -// } - -// /// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value. -// #[node_macro::node(category("Math: Trig"))] -// fn sine_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { -// if radians { -// value.asin() -// } else { -// value.asin().to_degrees() -// } -// } - -// /// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value. -// #[node_macro::node(category("Math: Trig"))] -// fn cosine_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { -// if radians { -// value.acos() -// } else { -// value.acos().to_degrees() -// } -// } - -// /// The inverse tangent trigonometric function (atan) calculates the angle whose tangent is the specified value. -// #[node_macro::node(category("Math: Trig"))] -// fn tangent_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { -// if radians { -// value.atan() -// } else { -// value.atan().to_degrees() -// } -// } - -// /// The inverse tangent trigonometric function (atan2) calculates the angle whose tangent is the ratio of the two specified values. -// #[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))] -// fn tangent_inverse_2_argument( -// _: impl Ctx, -// #[implementations(f64, f32)] y: U, -// #[expose] -// #[implementations(f64, f32)] -// x: U, -// radians: bool, -// ) -> U { -// if radians { -// y.atan2(x) -// } else { -// y.atan2(x).to_degrees() -// } -// } - -// /// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed. -// #[node_macro::node(category("Math: Numeric"))] -// fn random( -// _: impl Ctx, -// _primary: (), -// seed: u64, -// #[implementations(f64, f32)] -// #[default(0.)] -// min: U, -// #[implementations(f64, f32)] -// #[default(1.)] -// max: U, -// ) -> f64 { -// let mut rng = rand::rngs::StdRng::seed_from_u64(seed); -// let result = rng.gen::(); -// let (min, max) = if min < max { (min, max) } else { (max, min) }; -// let (min, max) = (min.to_f64().unwrap(), max.to_f64().unwrap()); -// result * (max - min) + min -// } - -// /// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. -// #[node_macro::node(name("To u32"), category("Math: Numeric"))] -// fn to_u32(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u32 { -// let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap()); -// value.to_u32().unwrap() -// } - -// /// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. -// #[node_macro::node(name("To u64"), category("Math: Numeric"))] -// fn to_u64(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u64 { -// let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap()); -// value.to_u64().unwrap() -// } - -// /// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero. -// #[node_macro::node(category("Math: Numeric"))] -// fn round(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { -// value.round() -// } - -// /// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole. -// #[node_macro::node(category("Math: Numeric"))] -// fn floor(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { -// value.floor() -// } - -// /// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole. -// #[node_macro::node(category("Math: Numeric"))] -// fn ceiling(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { -// value.ceil() -// } - -// /// The absolute value function (abs) removes the negative sign from an input value, if present. -// #[node_macro::node(category("Math: Numeric"))] -// fn absolute_value(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { -// value.abs() -// } - -// /// The minimum function (min) picks the smaller of two numbers. -// #[node_macro::node(category("Math: Numeric"))] -// fn min(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { -// match value < other_value { -// true => value, -// false => other_value, -// } -// } - -// /// The maximum function (max) picks the larger of two numbers. -// #[node_macro::node(category("Math: Numeric"))] -// fn max(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { -// match value > other_value { -// true => value, -// false => other_value, -// } -// } - -// /// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed. -// #[node_macro::node(category("Math: Numeric"))] -// fn clamp( -// _: impl Ctx, -// #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, -// #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T, -// #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T, -// ) -> T { -// let (min, max) = if min < max { (min, max) } else { (max, min) }; -// if value < min { -// min -// } else if value > max { -// max -// } else { -// value -// } -// } - -// /// The equality operation (==) compares two values and returns true if they are equal, or false if they are not. -// #[node_macro::node(category("Math: Logic"))] -// 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 -// } - -// /// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are. -// #[node_macro::node(category("Math: Logic"))] -// 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 -// } - -// /// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false. -// #[node_macro::node(category("Math: Logic"))] -// fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool { -// value || other_value -// } - -// /// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false. -// #[node_macro::node(category("Math: Logic"))] -// fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool { -// value && other_value -// } - -// /// The logical not operation (!) reverses true and false value of the input. -// #[node_macro::node(category("Math: Logic"))] -// fn logical_not(_: impl Ctx, input: bool) -> bool { -// !input -// } - -// /// Constructs a bool value which may be set to true or false. -// #[node_macro::node(category("Value"))] -// fn bool_value(_: impl Ctx, _primary: (), #[name("Bool")] bool_value: bool) -> bool { -// bool_value -// } - -// /// Constructs a number value which may be set to any real number. -// #[node_macro::node(category("Value"))] -// fn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 { -// number -// } - -// /// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider. -// #[node_macro::node(category("Value"))] -// fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 { -// percentage -// } - -// /// Constructs a two-dimensional vector value which may be set to any XY coordinate. -// #[node_macro::node(category("Value"))] -// fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 { -// DVec2::new(x, y) -// } - -// /// Constructs a color value which may be set to any color, or no color. -// #[node_macro::node(category("Value"))] -// fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option) -> Option { -// color -// } - -// /// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. -// #[node_macro::node(category("Value"))] -// fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops { -// gradient -// } - -// /// Constructs a blend mode choice value which may be set to any of the available blend modes in order to tell another node which blending operation to use. -// #[node_macro::node(category("Value"))] -// fn blend_mode_value(_: impl Ctx, _primary: (), blend_mode: BlendMode) -> BlendMode { -// blend_mode -// } - -// /// Meant for debugging purposes, not general use. Returns the size of the input type in bytes. -// #[cfg(feature = "std")] -// #[node_macro::node(category("Debug"))] -// fn size_of(_: impl Ctx, ty: crate::Type) -> Option { -// ty.size() -// } - -// /// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option. -// #[node_macro::node(category("Debug"))] -// fn some(_: impl Ctx, #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option { -// Some(input) -// } - -// /// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None. -// #[node_macro::node(category("Debug"))] -// fn unwrap(_: impl Ctx, #[implementations(Option, Option, Option, Option, Option, Option)] input: Option) -> T { -// input.unwrap_or_default() -// } - -// /// Meant for debugging purposes, not general use. Clones the input value. -// #[node_macro::node(category("Debug"))] -// fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&ImageFrame)] value: &'i T) -> T { -// value.clone() -// } - -// #[node_macro::node(category("Math: Vector"))] -// fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 { -// vector_a.dot(vector_b) -// } - -// // TODO: Rename to "Passthrough" -// /// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. -// #[node_macro::node(skip_impl)] -// fn identity<'i, T: 'i>(value: T) -> T { -// value -// } +/// The subtraction operation (-) calculates the difference between two numbers. +#[node_macro::node(category("Math: Arithmetic"))] +fn subtract, T>( + _: impl Ctx, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U, + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T, +) -> >::Output { + minuend - subtrahend +} + +/// The multiplication operation (×) calculates the product of two numbers. +#[node_macro::node(category("Math: Arithmetic"))] +fn multiply, T>( + _: impl Ctx, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U, + #[default(1.)] + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] + multiplicand: T, +) -> >::Output { + multiplier * multiplicand +} + +/// The division operation (÷) calculates the quotient of two numbers. +#[node_macro::node(category("Math: Arithmetic"))] +fn divide, T>( + _: impl Ctx, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, + #[default(1.)] + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] + denominator: T, +) -> >::Output { + numerator / denominator +} + +/// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled. +#[node_macro::node(category("Math: Arithmetic"))] +fn modulo>>, T: Copy>( + _: impl Ctx, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, + #[default(2.)] + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] + modulus: T, + always_positive: bool, +) -> >::Output { + if always_positive { + (numerator % modulus + modulus) % modulus + } else { + numerator % modulus + } +} + +/// The exponent operation (^) calculates the result of raising a number to a power. +#[node_macro::node(category("Math: Arithmetic"))] +fn exponent, T>( + _: impl Ctx, + #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U, + #[default(2.)] + #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)] + power: T, +) -> >::Output { + base.pow(power) +} + +/// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n. +#[node_macro::node(category("Math: Arithmetic"))] +fn root( + _: impl Ctx, + #[default(2.)] + #[implementations(f64, f32)] + radicand: U, + #[default(2.)] + #[implementations(f64, f32)] + degree: U, +) -> U { + if degree == U::from(2.).unwrap() { + radicand.sqrt() + } else if degree == U::from(3.).unwrap() { + radicand.cbrt() + } else { + radicand.powf(U::from(1.).unwrap() / degree) + } +} + +/// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e". +#[node_macro::node(category("Math: Arithmetic"))] +fn logarithm( + _: impl Ctx, + #[implementations(f64, f32)] value: U, + #[default(2.)] + #[implementations(f64, f32)] + base: U, +) -> U { + if base == U::from(2.).unwrap() { + value.log2() + } else if base == U::from(10.).unwrap() { + value.log10() + } else if base - U::from(std::f64::consts::E).unwrap() < U::epsilon() * U::from(1e6).unwrap() { + value.ln() + } else { + value.log(base) + } +} + +/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length. +#[node_macro::node(category("Math: Trig"))] +fn sine(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { + if radians { + theta.sin() + } else { + theta.to_radians().sin() + } +} + +/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length. +#[node_macro::node(category("Math: Trig"))] +fn cosine(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { + if radians { + theta.cos() + } else { + theta.to_radians().cos() + } +} + +/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length. +#[node_macro::node(category("Math: Trig"))] +fn tangent(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { + if radians { + theta.tan() + } else { + theta.to_radians().tan() + } +} + +/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value. +#[node_macro::node(category("Math: Trig"))] +fn sine_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { + if radians { + value.asin() + } else { + value.asin().to_degrees() + } +} + +/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value. +#[node_macro::node(category("Math: Trig"))] +fn cosine_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { + if radians { + value.acos() + } else { + value.acos().to_degrees() + } +} + +/// The inverse tangent trigonometric function (atan) calculates the angle whose tangent is the specified value. +#[node_macro::node(category("Math: Trig"))] +fn tangent_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { + if radians { + value.atan() + } else { + value.atan().to_degrees() + } +} + +/// The inverse tangent trigonometric function (atan2) calculates the angle whose tangent is the ratio of the two specified values. +#[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))] +fn tangent_inverse_2_argument( + _: impl Ctx, + #[implementations(f64, f32)] y: U, + #[expose] + #[implementations(f64, f32)] + x: U, + radians: bool, +) -> U { + if radians { + y.atan2(x) + } else { + y.atan2(x).to_degrees() + } +} + +/// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed. +#[node_macro::node(category("Math: Numeric"))] +fn random( + _: impl Ctx, + _primary: (), + seed: u64, + #[implementations(f64, f32)] + #[default(0.)] + min: U, + #[implementations(f64, f32)] + #[default(1.)] + max: U, +) -> f64 { + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + let result = rng.gen::(); + let (min, max) = if min < max { (min, max) } else { (max, min) }; + let (min, max) = (min.to_f64().unwrap(), max.to_f64().unwrap()); + result * (max - min) + min +} + +/// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. +#[node_macro::node(name("To u32"), category("Math: Numeric"))] +fn to_u32(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u32 { + let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap()); + value.to_u32().unwrap() +} + +/// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. +#[node_macro::node(name("To u64"), category("Math: Numeric"))] +fn to_u64(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u64 { + let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap()); + value.to_u64().unwrap() +} + +/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero. +#[node_macro::node(category("Math: Numeric"))] +fn round(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { + value.round() +} + +/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole. +#[node_macro::node(category("Math: Numeric"))] +fn floor(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { + value.floor() +} + +/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole. +#[node_macro::node(category("Math: Numeric"))] +fn ceiling(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { + value.ceil() +} + +/// The absolute value function (abs) removes the negative sign from an input value, if present. +#[node_macro::node(category("Math: Numeric"))] +fn absolute_value(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { + value.abs() +} + +/// The minimum function (min) picks the smaller of two numbers. +#[node_macro::node(category("Math: Numeric"))] +fn min(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { + match value < other_value { + true => value, + false => other_value, + } +} + +/// The maximum function (max) picks the larger of two numbers. +#[node_macro::node(category("Math: Numeric"))] +fn max(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { + match value > other_value { + true => value, + false => other_value, + } +} + +/// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed. +#[node_macro::node(category("Math: Numeric"))] +fn clamp( + _: impl Ctx, + #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, + #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T, + #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T, +) -> T { + let (min, max) = if min < max { (min, max) } else { (max, min) }; + if value < min { + min + } else if value > max { + max + } else { + value + } +} + +/// The equality operation (==) compares two values and returns true if they are equal, or false if they are not. +#[node_macro::node(category("Math: Logic"))] +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 +} + +/// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are. +#[node_macro::node(category("Math: Logic"))] +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 +} + +/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false. +#[node_macro::node(category("Math: Logic"))] +fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool { + value || other_value +} + +/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false. +#[node_macro::node(category("Math: Logic"))] +fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool { + value && other_value +} + +/// The logical not operation (!) reverses true and false value of the input. +#[node_macro::node(category("Math: Logic"))] +fn logical_not(_: impl Ctx, input: bool) -> bool { + !input +} + +/// Constructs a bool value which may be set to true or false. +#[node_macro::node(category("Value"))] +fn bool_value(_: impl Ctx, _primary: (), #[name("Bool")] bool_value: bool) -> bool { + bool_value +} + +/// Constructs a number value which may be set to any real number. +#[node_macro::node(category("Value"))] +fn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 { + number +} + +/// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider. +#[node_macro::node(category("Value"))] +fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 { + percentage +} + +/// Constructs a two-dimensional vector value which may be set to any XY coordinate. +#[node_macro::node(category("Value"))] +fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 { + DVec2::new(x, y) +} + +/// Constructs a color value which may be set to any color, or no color. +#[node_macro::node(category("Value"))] +fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option) -> Option { + color +} + +/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. +#[node_macro::node(category("Value"))] +fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops { + gradient +} + +/// Constructs a blend mode choice value which may be set to any of the available blend modes in order to tell another node which blending operation to use. +#[node_macro::node(category("Value"))] +fn blend_mode_value(_: impl Ctx, _primary: (), blend_mode: BlendMode) -> BlendMode { + blend_mode +} + +/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes. +#[cfg(feature = "std")] +#[node_macro::node(category("Debug"))] +fn size_of(_: impl Ctx, ty: crate::Type) -> Option { + ty.size() +} + +/// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option. +#[node_macro::node(category("Debug"))] +fn some(_: impl Ctx, #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option { + Some(input) +} + +/// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None. +#[node_macro::node(category("Debug"))] +fn unwrap(_: impl Ctx, #[implementations(Option, Option, Option, Option, Option, Option)] input: Option) -> T { + input.unwrap_or_default() +} + +/// Meant for debugging purposes, not general use. Clones the input value. +#[node_macro::node(category("Debug"))] +fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&ImageFrame)] value: &'i T) -> T { + value.clone() +} + +#[node_macro::node(category("Math: Vector"))] +fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 { + vector_a.dot(vector_b) +} + +// TODO: Rename to "Passthrough" +/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. +#[node_macro::node(skip_impl)] +fn identity<'i, T: 'i>(value: T) -> T { + value +} // Type // TODO: Document this @@ -536,51 +535,51 @@ where input.into() } -// #[cfg(test)] -// mod test { -// use super::*; -// use crate::{generic::*, structural::*, value::*}; - -// #[test] -// pub fn dot_product_function() { -// let vector_a = glam::DVec2::new(1., 2.); -// let vector_b = glam::DVec2::new(3., 4.); -// assert_eq!(dot_product(vector_a, vector_b), 11.); -// } - -// #[test] -// fn test_basic_expression() { -// let result = math((), 0., "2 + 2".to_string(), 0.); -// assert_eq!(result, 4.); -// } - -// #[test] -// fn test_complex_expression() { -// let result = math((), 0., "(5 * 3) + (10 / 2)".to_string(), 0.); -// assert_eq!(result, 20.); -// } - -// #[test] -// fn test_default_expression() { -// let result = math((), 0., "0".to_string(), 0.); -// assert_eq!(result, 0.); -// } - -// #[test] -// fn test_invalid_expression() { -// let result = math((), 0., "invalid".to_string(), 0.); -// assert_eq!(result, 0.); -// } - -// #[test] -// pub fn identity_node() { -// let value = ValueNode(4u32).then(IdentityNode::new()); -// assert_eq!(value.eval(()), &4); -// } - -// #[test] -// pub fn foo() { -// let fnn = FnNode::new(|(a, b)| (b, a)); -// assert_eq!(fnn.eval((1u32, 2u32)), (2, 1)); -// } -// } +#[cfg(test)] +mod test { + use super::*; + use crate::{generic::*, structural::*, value::*}; + + #[test] + pub fn dot_product_function() { + let vector_a = glam::DVec2::new(1., 2.); + let vector_b = glam::DVec2::new(3., 4.); + assert_eq!(dot_product(vector_a, vector_b), 11.); + } + + #[test] + fn test_basic_expression() { + let result = math((), 0., "2 + 2".to_string(), 0.); + assert_eq!(result, 4.); + } + + #[test] + fn test_complex_expression() { + let result = math((), 0., "(5 * 3) + (10 / 2)".to_string(), 0.); + assert_eq!(result, 20.); + } + + #[test] + fn test_default_expression() { + let result = math((), 0., "0".to_string(), 0.); + assert_eq!(result, 0.); + } + + #[test] + fn test_invalid_expression() { + let result = math((), 0., "invalid".to_string(), 0.); + assert_eq!(result, 0.); + } + + #[test] + pub fn identity_node() { + let value = ValueNode(4u32).then(IdentityNode::new()); + assert_eq!(value.eval(()), &4); + } + + #[test] + pub fn foo() { + let fnn = FnNode::new(|(a, b)| (b, a)); + assert_eq!(fnn.eval((1u32, 2u32)), (2, 1)); + } +} diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index bc086dc69d..36614054b2 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -2,7 +2,7 @@ use crate::application_io::TextureFrame; use crate::raster::bbox::AxisAlignedBbox; use crate::raster::{ImageFrame, Pixel}; use crate::vector::VectorData; -use crate::{Artboard, ArtboardGroup, Color, ContextImpl, Ctx, ExtractFootprint, GraphicElement, GraphicGroup}; +use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, ContextImpl, Ctx, ExtractAll, ExtractFootprint, GraphicElement, GraphicGroup}; use crate::{Context, OwnedContextImpl}; use glam::{DAffine2, DVec2}; @@ -212,82 +212,9 @@ impl ApplyTransform for () { fn apply_transform(&mut self, &_modification: &DAffine2) {} } -// async fn transform2<'call, 'input: 'call, 'n: 'call, T: 'n + TransformMut, _Input: ExtractFootprint + 'call>( -// input: _Input, -// transform_target: impl crate::Node<'call, Context<'call>, Output = T> + 'n, -// ) -> T -// where -// { -// let footprint = input.footprint().unwrap(); -// let ctx: ContextImpl<'_> = ContextImpl { -// footprint: Some(&footprint), -// ..Default::default() -// }; -// let mut transform_target = transform_target.eval(Some(&ctx)); -// transform_target -// } - -// struct Context2<'a>(&'a str); - -// async fn transform2<'call, 'n: 'call, T: 'n>( -// // input: _Input, -// transform_target: &'n (impl for<'all_input> crate::Node<'all_input, Context2<'all_input>, Output: core::future::Future> + Sync), -// ) -> T { -// // // let footprint = *input.footprint().unwrap(); -// // let ctx: ContextImpl<'_> = ContextImpl { -// // // footprint: Some(&footprint), -// // ..Default::default() -// // }; -// // let transform_target = transform_target.eval(Some(&ctx)).await; -// // transform_target -// let string = String::from("test"); - -// transform_target.eval(Context2(&string.as_ref())).await -// } - -// async fn transform3<'call, 'n: 'call, T: 'n>(transform_target: impl for<'all_input> crate::Node<'all_input, Context2<'all_input>, Output = impl core::future::Future> + Sync) -> T { -// let string = String::from("test"); - -// transform_target.eval(Context2(&string.as_ref())).await -// } - -// // impl<'call, 'n: 'call, T: 'n, _Input: 'n, Node0> crate::Node<'n, _Input> for TransformNode -// // where -// // Node0: for<'all_input> crate::Node<'all_input, Context2<'all_input>, Output: core::future::Future>, -// // // for<'a, 'b, 'c> >>>::Output: crate::WasmNotSend, -// // Node0: Sync + Send, -// // { -// // type Output = core::pin::Pin + 'n>>; -// // #[inline] -// // fn eval(&'n self, __input: _Input) -> Self::Output { -// // let transform_target = &self.transform_target; -// // Box::pin(self::transform3(transform_target)) -// // } -// // } - -// // impl<'call, 'n: 'call, T: 'n, _Input: 'n + ExtractFootprint, Node0> crate::Node<'n, _Input> for TransformNode -// impl<'call, 'n: 'call, T: 'n, _Input: 'n, Node0, F0> crate::Node<'n, _Input> for TransformNode -// where -// Node0: for<'all_input> crate::Node<'all_input, Context2<'all_input>, Output = F0>, -// F0: core::future::Future + Send, -// // for<'a, 'b, 'c> >>>::Output: crate::WasmNotSend, -// Node0: Sync + Send, -// { -// // type Output = crate::registry::DynFuture<'n, T>; -// type Output = core::pin::Pin + 'n + Send>>; -// #[inline] -// fn eval(&'n self, __input: _Input) -> Self::Output { -// let transform_target = &self.transform_target; -// Box::pin(self::transform3(transform_target)) -// } -// } - -// pub struct TransformNode { -// pub(super) transform_target: Node0, -// } #[node_macro::node(category(""))] async fn transform( - input: impl ExtractFootprint + Ctx, + input: impl ExtractAll + CloneVarArgs + Ctx, #[implementations( Context -> VectorData, Context -> GraphicGroup, @@ -304,15 +231,13 @@ async fn transform( where { let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]); - let mut footprint = *input.footprint().unwrap(); + let mut footprint = *input.try_footprint().unwrap(); if !footprint.ignore_modifications { footprint.apply_transform(&modification); } - let ctx = OwnedContextImpl { - footprint: Some(footprint), - ..Default::default() - }; + let mut ctx = OwnedContextImpl::from(input); + ctx.set_footprint(footprint); let mut transform_target = transform_target.eval(Some(ctx.into())).await; diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 3eb78a222b..1c08a5f575 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -9,7 +9,7 @@ use graphene_core::raster::{ Sample, }; use graphene_core::transform::{Footprint, Transform}; -use graphene_core::{AlphaBlending, Color, Node}; +use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node}; use fastnoise_lite; use glam::{DAffine2, DVec2, Vec2}; @@ -33,11 +33,12 @@ impl From for Error { } #[node_macro::node(category("Debug: Raster"))] -fn sample_image(footprint: Footprint, image_frame: ImageFrame) -> ImageFrame { +fn sample_image(ctx: impl ExtractFootprint + Clone, image_frame: ImageFrame) -> ImageFrame { // Resize the image using the image crate let image = image_frame.image; let data = bytemuck::cast_vec(image.data); + let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox(); let intersection = viewport_bounds.intersect(&image_bounds); @@ -318,7 +319,7 @@ fn extend_image_to_bounds(image: ImageFrame, bounds: DAffine2) -> ImageFr } #[node_macro::node(category("Debug: Raster"))] -fn empty_image(_: (), transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame

{ +fn empty_image(_: impl Ctx, transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame

{ let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32; @@ -447,7 +448,7 @@ fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> ImageFrame<_ #[node_macro::node(category("Raster: Generator"))] #[allow(clippy::too_many_arguments)] fn noise_pattern( - footprint: Footprint, + ctx: impl ExtractFootprint, clip: bool, seed: u32, scale: f64, @@ -464,6 +465,7 @@ fn noise_pattern( cellular_return_type: CellularReturnType, cellular_jitter: f64, ) -> ImageFrame { + let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); let mut size = viewport_bounds.size(); @@ -595,7 +597,8 @@ fn noise_pattern( } #[node_macro::node(category("Raster: Generator"))] -fn mandelbrot(footprint: Footprint) -> ImageFrame { +fn mandelbrot(ctx: impl ExtractFootprint) -> ImageFrame { + let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox(); diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index d28a8c0d86..85b7365adb 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -7,8 +7,7 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::{Comma, RArrow}; use syn::{ - parse_quote, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lifetime, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, - WhereClause, + parse_quote, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, WhereClause, }; use crate::codegen::generate_node_code; diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index f89c776f35..32e31b3569 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -918,7 +918,7 @@ async fn upload_texture<'a: 'n>( executor: &'a WgpuExecutor, ) -> TextureFrame { // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); - let footprint = footprint.footprint().copied(); + let footprint = footprint.try_footprint().copied(); let ctx = OwnedContextImpl { footprint, ..Default::default() }; let input = input.eval(Some(ctx.into())).await; let new_data = input.image.data.into_iter().map(SRGBA8::from).collect(); From 950873b5b56c7ba13c2f521dc1175512cda6c515 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 28 Jan 2025 13:22:04 +0100 Subject: [PATCH 07/34] Uncomment nodes --- node-graph/gcore/src/graphic_element.rs | 388 ++++++++++++------------ node-graph/gcore/src/lib.rs | 2 +- node-graph/gcore/src/vector/mod.rs | 6 +- 3 files changed, 198 insertions(+), 198 deletions(-) diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 865dffd136..967da9f42a 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -231,200 +231,200 @@ impl ArtboardGroup { } } -// #[node_macro::node(category(""))] -// async fn layer( -// #[implementations( -// (), -// Footprint, -// )] -// footprint: F, -// #[implementations( -// () -> GraphicGroup, -// Footprint -> GraphicGroup, -// )] -// stack: impl Node, -// #[implementations( -// () -> GraphicElement, -// Footprint -> GraphicElement, -// )] -// element: impl Node, -// node_path: Vec, -// ) -> GraphicGroup { -// let mut element = element.eval(footprint).await; -// let mut stack = stack.eval(footprint).await; -// if stack.transform.matrix2.determinant() != 0. { -// *element.transform_mut() = stack.transform.inverse() * element.transform(); -// } else { -// stack.clear(); -// stack.transform = DAffine2::IDENTITY; -// } - -// // Get the penultimate element of the node path, or None if the path is too short -// let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); -// stack.push((element, encapsulating_node_id)); -// stack -// } - -// #[node_macro::node(category("Debug"))] -// async fn to_element + 'n>( -// #[implementations( -// (), -// (), -// (), -// (), -// Footprint, -// )] -// footprint: F, -// #[implementations( -// () -> GraphicGroup, -// () -> VectorData, -// () -> ImageFrame, -// () -> TextureFrame, -// Footprint -> GraphicGroup, -// Footprint -> VectorData, -// Footprint -> ImageFrame, -// Footprint -> TextureFrame, -// )] -// data: impl Node, -// ) -> GraphicElement { -// data.eval(footprint).await.into() -// } - -// #[node_macro::node(category("General"))] -// async fn to_group + 'n>( -// #[implementations( -// (), -// (), -// (), -// (), -// Footprint, -// )] -// footprint: F, -// #[implementations( -// () -> GraphicGroup, -// () -> VectorData, -// () -> ImageFrame, -// () -> TextureFrame, -// Footprint -> GraphicGroup, -// Footprint -> VectorData, -// Footprint -> ImageFrame, -// Footprint -> TextureFrame, -// )] -// element: impl Node, -// ) -> GraphicGroup { -// element.eval(footprint).await.into() -// } - -// #[node_macro::node(category("General"))] -// async fn flatten_group( -// #[implementations( -// (), -// Footprint, -// )] -// footprint: F, -// #[implementations( -// () -> GraphicGroup, -// Footprint -> GraphicGroup, -// )] -// group: impl Node, -// fully_flatten: bool, -// ) -> GraphicGroup { -// let nested_group = group.eval(footprint).await; -// let mut flat_group = GraphicGroup::EMPTY; -// fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) { -// let mut collection_group = GraphicGroup::EMPTY; -// for (element, reference) in current_group.elements { -// if let GraphicElement::GraphicGroup(mut nested_group) = element { -// nested_group.transform *= current_group.transform; -// let mut sub_group = GraphicGroup::EMPTY; -// if fully_flatten { -// flatten_group(&mut sub_group, nested_group, fully_flatten); -// } else { -// for (collection_element, _) in &mut nested_group.elements { -// *collection_element.transform_mut() = nested_group.transform * collection_element.transform(); -// } -// sub_group = nested_group; -// } -// collection_group.append(&mut sub_group.elements); -// } else { -// collection_group.push((element, reference)); -// } -// } - -// result_group.append(&mut collection_group.elements); -// } -// flatten_group(&mut flat_group, nested_group, fully_flatten); -// flat_group -// } - -// #[node_macro::node(category(""))] -// async fn to_artboard + 'n>( -// #[implementations( -// (), -// (), -// (), -// (), -// Footprint, -// )] -// mut footprint: F, -// #[implementations( -// () -> GraphicGroup, -// () -> VectorData, -// () -> ImageFrame, -// () -> TextureFrame, -// Footprint -> GraphicGroup, -// Footprint -> VectorData, -// Footprint -> ImageFrame, -// Footprint -> TextureFrame, -// )] -// contents: impl Node, -// label: String, -// location: IVec2, -// dimensions: IVec2, -// background: Color, -// clip: bool, -// ) -> Artboard { -// footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2())); -// let graphic_group = contents.eval(footprint).await; - -// Artboard { -// graphic_group: graphic_group.into(), -// label, -// location: location.min(location + dimensions), -// dimensions: dimensions.abs(), -// background, -// clip, -// } -// } - -// #[node_macro::node(category(""))] -// async fn append_artboard( -// #[implementations( -// (), -// Footprint, -// )] -// footprint: F, -// #[implementations( -// () -> ArtboardGroup, -// Footprint -> ArtboardGroup, -// )] -// artboards: impl Node, -// #[implementations( -// () -> Artboard, -// Footprint -> Artboard, -// )] -// artboard: impl Node, -// node_path: Vec, -// ) -> ArtboardGroup { -// let artboard = artboard.eval(footprint).await; -// let mut artboards = artboards.eval(footprint).await; - -// // Get the penultimate element of the node path, or None if the path is too short -// let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); -// artboards.append_artboard(artboard, encapsulating_node_id); - -// artboards -// } +#[node_macro::node(category(""))] +async fn layer( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + Footprint -> GraphicGroup, + )] + stack: impl Node, + #[implementations( + () -> GraphicElement, + Footprint -> GraphicElement, + )] + element: impl Node, + node_path: Vec, +) -> GraphicGroup { + let mut element = element.eval(footprint).await; + let mut stack = stack.eval(footprint).await; + if stack.transform.matrix2.determinant() != 0. { + *element.transform_mut() = stack.transform.inverse() * element.transform(); + } else { + stack.clear(); + stack.transform = DAffine2::IDENTITY; + } + + // Get the penultimate element of the node path, or None if the path is too short + let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); + stack.push((element, encapsulating_node_id)); + stack +} + +#[node_macro::node(category("Debug"))] +async fn to_element + 'n>( + #[implementations( + (), + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + () -> VectorData, + () -> ImageFrame, + () -> TextureFrame, + Footprint -> GraphicGroup, + Footprint -> VectorData, + Footprint -> ImageFrame, + Footprint -> TextureFrame, + )] + data: impl Node, +) -> GraphicElement { + data.eval(footprint).await.into() +} + +#[node_macro::node(category("General"))] +async fn to_group + 'n>( + #[implementations( + (), + (), + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + () -> VectorData, + () -> ImageFrame, + () -> TextureFrame, + Footprint -> GraphicGroup, + Footprint -> VectorData, + Footprint -> ImageFrame, + Footprint -> TextureFrame, + )] + element: impl Node, +) -> GraphicGroup { + element.eval(footprint).await.into() +} + +#[node_macro::node(category("General"))] +async fn flatten_group( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> GraphicGroup, + Footprint -> GraphicGroup, + )] + group: impl Node, + fully_flatten: bool, +) -> GraphicGroup { + let nested_group = group.eval(footprint).await; + let mut flat_group = GraphicGroup::EMPTY; + fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) { + let mut collection_group = GraphicGroup::EMPTY; + for (element, reference) in current_group.elements { + if let GraphicElement::GraphicGroup(mut nested_group) = element { + nested_group.transform *= current_group.transform; + let mut sub_group = GraphicGroup::EMPTY; + if fully_flatten { + flatten_group(&mut sub_group, nested_group, fully_flatten); + } else { + for (collection_element, _) in &mut nested_group.elements { + *collection_element.transform_mut() = nested_group.transform * collection_element.transform(); + } + sub_group = nested_group; + } + collection_group.append(&mut sub_group.elements); + } else { + collection_group.push((element, reference)); + } + } + + result_group.append(&mut collection_group.elements); + } + flatten_group(&mut flat_group, nested_group, fully_flatten); + flat_group +} + +#[node_macro::node(category(""))] +async fn to_artboard + 'n>( + #[implementations( + (), + (), + (), + (), + Footprint, + )] + mut footprint: F, + #[implementations( + () -> GraphicGroup, + () -> VectorData, + () -> ImageFrame, + () -> TextureFrame, + Footprint -> GraphicGroup, + Footprint -> VectorData, + Footprint -> ImageFrame, + Footprint -> TextureFrame, + )] + contents: impl Node, + label: String, + location: IVec2, + dimensions: IVec2, + background: Color, + clip: bool, +) -> Artboard { + footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2())); + let graphic_group = contents.eval(footprint).await; + + Artboard { + graphic_group: graphic_group.into(), + label, + location: location.min(location + dimensions), + dimensions: dimensions.abs(), + background, + clip, + } +} + +#[node_macro::node(category(""))] +async fn append_artboard( + #[implementations( + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> ArtboardGroup, + Footprint -> ArtboardGroup, + )] + artboards: impl Node, + #[implementations( + () -> Artboard, + Footprint -> Artboard, + )] + artboard: impl Node, + node_path: Vec, +) -> ArtboardGroup { + let artboard = artboard.eval(footprint).await; + let mut artboards = artboards.eval(footprint).await; + + // Get the penultimate element of the node path, or None if the path is too short + let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); + artboards.append_artboard(artboard, encapsulating_node_id); + + artboards +} impl From> for GraphicElement { fn from(image_frame: ImageFrame) -> Self { diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 8672f85620..4233e9a360 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -16,7 +16,7 @@ pub use ctor; pub mod consts; pub mod context; pub mod generic; -// pub mod logic; +pub mod logic; pub mod ops; pub mod structural; #[cfg(feature = "std")] diff --git a/node-graph/gcore/src/vector/mod.rs b/node-graph/gcore/src/vector/mod.rs index 1aa72f4524..e729eede53 100644 --- a/node-graph/gcore/src/vector/mod.rs +++ b/node-graph/gcore/src/vector/mod.rs @@ -1,5 +1,5 @@ pub mod brush_stroke; -// pub mod generator_nodes; +pub mod generator_nodes; pub mod misc; pub mod style; @@ -8,7 +8,7 @@ pub use style::PathStyle; mod vector_data; pub use vector_data::*; -// mod vector_nodes; -// pub use vector_nodes::*; +mod vector_nodes; +pub use vector_nodes::*; pub use bezier_rs; From 551624f17db5f1f9acddf0a58721f74491ff98d5 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 28 Jan 2025 13:43:42 +0100 Subject: [PATCH 08/34] Port more nodes --- node-graph/gcore/src/graphic_element.rs | 93 ++++++------------------- node-graph/gcore/src/logic.rs | 8 +-- 2 files changed, 24 insertions(+), 77 deletions(-) diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 967da9f42a..ba47667176 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -3,7 +3,7 @@ use crate::raster::{BlendMode, ImageFrame}; use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut}; use crate::uuid::NodeId; use crate::vector::VectorData; -use crate::Color; +use crate::{Color, Ctx}; use dyn_any::DynAny; @@ -232,26 +232,7 @@ impl ArtboardGroup { } #[node_macro::node(category(""))] -async fn layer( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, - )] - stack: impl Node, - #[implementations( - () -> GraphicElement, - Footprint -> GraphicElement, - )] - element: impl Node, - node_path: Vec, -) -> GraphicGroup { - let mut element = element.eval(footprint).await; - let mut stack = stack.eval(footprint).await; +async fn layer(_: impl Ctx, mut stack: GraphicGroup, mut element: GraphicElement, node_path: Vec) -> GraphicGroup { if stack.transform.matrix2.determinant() != 0. { *element.transform_mut() = stack.transform.inverse() * element.transform(); } else { @@ -266,70 +247,36 @@ async fn layer( } #[node_macro::node(category("Debug"))] -async fn to_element + 'n>( +async fn to_element + 'n>( + _: impl Ctx, #[implementations( - (), - (), - (), - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> TextureFrame, + GraphicGroup, + VectorData, + ImageFrame, + TextureFrame, )] - data: impl Node, + data: Data, ) -> GraphicElement { - data.eval(footprint).await.into() + data.into() } #[node_macro::node(category("General"))] -async fn to_group + 'n>( - #[implementations( - (), - (), - (), - (), - Footprint, - )] - footprint: F, +async fn to_group + 'n>( + _: impl Ctx, #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> TextureFrame, + GraphicGroup, + VectorData, + ImageFrame, + TextureFrame, )] - element: impl Node, + element: Data, ) -> GraphicGroup { - element.eval(footprint).await.into() + element.into() } #[node_macro::node(category("General"))] -async fn flatten_group( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, - )] - group: impl Node, - fully_flatten: bool, -) -> GraphicGroup { - let nested_group = group.eval(footprint).await; +async fn flatten_group(_: impl Ctx, group: GraphicGroup, fully_flatten: bool) -> GraphicGroup { + let nested_group = group; let mut flat_group = GraphicGroup::EMPTY; fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) { let mut collection_group = GraphicGroup::EMPTY; diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index cad2842372..2b8b31518f 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -1,9 +1,9 @@ -use crate::vector::VectorData; use crate::Context; +use crate::{vector::VectorData, Ctx}; use glam::{DAffine2, DVec2}; #[node_macro::node(category("Debug"))] -fn log_to_console(#[implementations(Context)] _: F, #[implementations(String, bool, f64, u32, u64, DVec2, VectorData, DAffine2)] value: T) -> T { +fn log_to_console(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorData, DAffine2)] value: T) -> T { #[cfg(not(target_arch = "spirv"))] // KEEP THIS `debug!()` - It acts as the output for the debug node itself debug!("{:#?}", value); @@ -11,12 +11,12 @@ fn log_to_console(#[implementations(Context)] } #[node_macro::node(category("Debug"))] -fn to_string(#[implementations(Context)] _: F, #[implementations(String, bool, f64, u32, u64, DVec2, VectorData, DAffine2)] value: T) -> String { +fn to_string(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorData, DAffine2)] value: T) -> String { format!("{:?}", value) } #[node_macro::node(category("Debug"))] -async fn switch( +async fn switch( #[implementations(Context)] footprint: F, condition: bool, #[expose] From 7a67e20947ceb316521b85674b8762c1fd117932 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 28 Jan 2025 15:18:40 +0100 Subject: [PATCH 09/34] Port vector nodes --- node-graph/gcore/src/context.rs | 54 +-- node-graph/gcore/src/graphic_element.rs | 58 +-- node-graph/gcore/src/vector/vector_nodes.rs | 379 +++----------------- 3 files changed, 71 insertions(+), 420 deletions(-) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 960764cd5b..1233aac0c4 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -235,6 +235,13 @@ impl OwnedContextImpl { pub fn set_footprint(&mut self, footprint: Footprint) { self.footprint = Some(footprint); } + pub fn with_footprint(mut self, footprint: Footprint) -> Self { + self.footprint = Some(footprint); + self + } + pub fn into_context(self) -> Option> { + Some(Arc::new(self)) + } } #[derive(Default, Clone, Copy, dyn_any::DynAny)] @@ -257,51 +264,4 @@ impl<'a> ContextImpl<'a> { ..*self } } - // #[cfg(feature = "alloc")] - // pub fn reborrow_var_args_to_vec<'short>(&self) -> Option]>> - // where - // 'a: 'short, - // { - // self.varargs.map(|x| shorten_lifetime_to_vec(x).into()) - // } - // pub fn reborrow_var_args_to_buffer<'short, const N: usize>(&self, buffer: &'short mut [DynRef<'short>; N]) -> Option<&'short [DynRef<'short>]> - // where - // 'a: 'short, - // { - // self.varargs.map(|x| shorten_lifetime_to_buffer(x, buffer)) - // } } - -// fn shorten_lifetime_to_vec<'c, 'b: 'c>(input: &'b [DynRef<'b>]) -> Vec> { -// input.iter().map(|&x| x.reborrow_ref()).collect() -// } -// fn shorten_lifetime_to_buffer<'c, 'b: 'c, const N: usize>(input: &'b [DynRef<'b>], buffer: &'c mut [DynRef<'c>; N]) -> &'c [DynRef<'c>] { -// let iter = input.iter().map(|&x| x.reborrow_ref()).zip(buffer.iter_mut()); -// if input.len() > N { -// unreachable!("Insufficient buffer size for varargs"); -// } -// for (data, buffer_slot) in iter { -// *buffer_slot = data.reborrow_ref(); -// } -// &buffer[..input.len()] -// } - -// #[test] -// fn shorten_lifetime_compile_test() { -// let context: ContextImpl<'static> = const { -// ContextImpl { -// footprint: None, -// varargs: None, -// index: None, -// time: None, -// } -// }; -// let footprint = Footprint::default(); -// let local_varargs = context.reborrow_var_args_to_vec(); -// let out = context.with_footprint(&footprint, local_varargs.as_ref()); -// assert!(out.footprint().is_some()); -// let mut buffer: [_; 0] = []; -// let local_varargs_buf = context.reborrow_var_args_to_buffer(&mut buffer); -// let out = context.with_footprint(&footprint, local_varargs_buf.as_ref()); -// assert!(out.footprint().is_some()); -// } diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index ba47667176..038ae98dc9 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -1,9 +1,10 @@ use crate::application_io::TextureFrame; use crate::raster::{BlendMode, ImageFrame}; -use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut}; +use crate::transform::{Transform, TransformMut}; use crate::uuid::NodeId; use crate::vector::VectorData; -use crate::{Color, Ctx}; +use crate::Context; +use crate::{CloneVarArgs, Color, Ctx, ExtractAll, OwnedContextImpl}; use dyn_any::DynAny; @@ -305,34 +306,25 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroup, fully_flatten: bool) -> } #[node_macro::node(category(""))] -async fn to_artboard + 'n>( +async fn to_artboard + 'n>( + ctx: impl ExtractAll + CloneVarArgs + Ctx, #[implementations( - (), - (), - (), - (), - Footprint, + Context -> GraphicGroup, + Context -> VectorData, + Context -> ImageFrame, + Context -> TextureFrame, )] - mut footprint: F, - #[implementations( - () -> GraphicGroup, - () -> VectorData, - () -> ImageFrame, - () -> TextureFrame, - Footprint -> GraphicGroup, - Footprint -> VectorData, - Footprint -> ImageFrame, - Footprint -> TextureFrame, - )] - contents: impl Node, + contents: impl Node, Output = Data>, label: String, location: IVec2, dimensions: IVec2, background: Color, clip: bool, ) -> Artboard { - footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2())); - let graphic_group = contents.eval(footprint).await; + let mut footprint = *ctx.footprint(); + footprint.translate(location.as_dvec2()); + let new_ctx = OwnedContextImpl::from(ctx).with_footprint(footprint); + let graphic_group = contents.eval(new_ctx.into_context()).await; Artboard { graphic_group: graphic_group.into(), @@ -345,27 +337,7 @@ async fn to_artboard + ' } #[node_macro::node(category(""))] -async fn append_artboard( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> ArtboardGroup, - Footprint -> ArtboardGroup, - )] - artboards: impl Node, - #[implementations( - () -> Artboard, - Footprint -> Artboard, - )] - artboard: impl Node, - node_path: Vec, -) -> ArtboardGroup { - let artboard = artboard.eval(footprint).await; - let mut artboards = artboards.eval(footprint).await; - +async fn append_artboard(_: impl Ctx, mut artboards: ArtboardGroup, artboard: Artboard, node_path: Vec) -> ArtboardGroup { // Get the penultimate element of the node path, or None if the path is too short let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); artboards.append_artboard(artboard, encapsulating_node_id); diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index d7a16d2ef3..ee87688bf8 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -6,7 +6,7 @@ use crate::renderer::GraphicElementRendered; use crate::transform::{Footprint, Transform, TransformMut}; use crate::vector::style::LineJoin; use crate::vector::PointDomain; -use crate::{Color, GraphicElement, GraphicGroup}; +use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, OwnedContextImpl}; use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue}; use glam::{DAffine2, DVec2}; @@ -37,20 +37,9 @@ impl VectorIterMut for VectorData { } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn assign_colors( - #[implementations( - (), - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - () -> VectorData, - Footprint -> GraphicGroup, - Footprint -> VectorData, - )] - vector_group: impl Node, +async fn assign_colors( + _: impl Ctx, + #[implementations(GraphicGroup, VectorData)] mut vector_group: T, #[default(true)] fill: bool, stroke: bool, gradient: GradientStops, @@ -59,13 +48,12 @@ async fn assign_colors( seed: SeedValue, repeat_every: u32, ) -> T { - let mut input = vector_group.eval(footprint).await; - let length = input.vector_iter_mut().count(); + let length = vector_group.vector_iter_mut().count(); let gradient = if reverse { gradient.reversed() } else { gradient }; let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); - for (i, (vector_data, _)) in input.vector_iter_mut().enumerate() { + for (i, (vector_data, _)) in vector_group.vector_iter_mut().enumerate() { let factor = match randomize { true => rng.gen::(), false => match repeat_every { @@ -86,49 +74,13 @@ async fn assign_colors( } } } - input + vector_group } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn fill + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( - #[implementations( - (), - (), - (), - (), - (), - (), - (), - (), - Footprint, - Footprint, - Footprint, - Footprint, - Footprint, - Footprint, - Footprint, - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - () -> VectorData, - () -> VectorData, - () -> VectorData, - () -> GraphicGroup, - () -> GraphicGroup, - () -> GraphicGroup, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> VectorData, - Footprint -> VectorData, - Footprint -> VectorData, - Footprint -> GraphicGroup, - Footprint -> GraphicGroup, - Footprint -> GraphicGroup, - Footprint -> GraphicGroup, - )] - vector_data: impl Node, +async fn fill + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( + _: impl Ctx, + #[implementations(VectorData, VectorData, VectorData, VectorData, GraphicGroup, GraphicGroup, GraphicGroup, GraphicGroup)] mut vector_data: TargetTy, #[implementations( Fill, Option, @@ -138,62 +90,29 @@ async fn fill + 'n + Send, TargetTy: VectorIter Option, Color, Gradient, - Fill, - Option, - Color, - Gradient, - Fill, - Option, - Color, - Gradient, )] #[default(Color::BLACK)] fill: FillTy, _backup_color: Option, _backup_gradient: Gradient, ) -> TargetTy { - let mut target = vector_data.eval(footprint).await; let fill: Fill = fill.into(); - for (target, _transform) in target.vector_iter_mut() { + for (target, _transform) in vector_data.vector_iter_mut() { target.style.set_fill(fill.clone()); } - target + vector_data } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn stroke> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( - #[implementations( - (), - (), - (), - (), - Footprint, - Footprint, - Footprint, - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - () -> VectorData, - () -> GraphicGroup, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> VectorData, - Footprint -> GraphicGroup, - Footprint -> GraphicGroup, - )] - vector_data: impl Node, +async fn stroke> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( + _: impl Ctx, + #[implementations(VectorData, VectorData, GraphicGroup, GraphicGroup)] mut vector_data: TargetTy, #[implementations( Option, Color, Option, Color, - Option, - Color, - Option, - Color, )] #[default(Color::BLACK)] color: ColorTy, @@ -204,7 +123,6 @@ async fn stroke> + 'n + Send, TargetTy line_join: LineJoin, #[default(4.)] miter_limit: f64, ) -> TargetTy { - let mut target = vector_data.eval(footprint).await; let stroke = Stroke { color: color.into(), weight, @@ -215,37 +133,24 @@ async fn stroke> + 'n + Send, TargetTy line_join_miter_limit: miter_limit, transform: DAffine2::IDENTITY, }; - for (target, transform) in target.vector_iter_mut() { + for (target, transform) in vector_data.vector_iter_mut() { target.style.set_stroke(Stroke { transform, ..stroke.clone() }); } - target + vector_data } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn repeat( - #[implementations( - (), - (), - Footprint, - Footprint, - )] - footprint: F, +async fn repeat( + _: impl Ctx, // TODO: Implement other GraphicElementRendered types. - #[implementations( - () -> VectorData, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> GraphicGroup, - )] - instance: impl Node, + #[implementations(VectorData, GraphicGroup)] instance: I, #[default(100., 100.)] // TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed. direction: DVec2, angle: Angle, #[default(4)] instances: IntegerCount, ) -> GraphicGroup { - let instance = instance.eval(footprint).await; let first_vector_transform = instance.transform(); let angle = angle.to_radians(); @@ -276,27 +181,14 @@ async fn repeat( - #[implementations( - (), - (), - Footprint, - Footprint, - )] - footprint: F, +async fn circular_repeat( + _: impl Ctx, // TODO: Implement other GraphicElementRendered types. - #[implementations( - () -> VectorData, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> GraphicGroup, - )] - instance: impl Node, + #[implementations(VectorData, GraphicGroup)] instance: I, angle_offset: Angle, #[default(5)] radius: f64, #[default(5)] instances: IntegerCount, ) -> GraphicGroup { - let instance = instance.eval(footprint).await; let first_vector_transform = instance.transform(); let instances = instances.max(1); @@ -325,27 +217,12 @@ async fn circular_repeat( - #[implementations( - (), - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - () -> VectorData, - Footprint -> VectorData, - )] - points: impl Node, +async fn copy_to_points( + _: impl Ctx, + points: VectorData, #[expose] - #[implementations( - () -> VectorData, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> GraphicGroup, - )] - instance: impl Node, + #[implementations(VectorData, GraphicGroup)] + instance: I, #[default(1)] random_scale_min: f64, #[default(1)] random_scale_max: f64, random_scale_bias: f64, @@ -353,8 +230,6 @@ async fn copy_to_points GraphicGroup { - let points = points.eval(footprint).await; - let instance = instance.eval(footprint).await; let instance_transform = instance.transform(); let random_scale_difference = random_scale_max - random_scale_min; @@ -409,20 +284,7 @@ async fn copy_to_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, -) -> VectorData { - let vector_data = vector_data.eval(footprint).await; - +async fn bounding_box(_: impl Ctx, vector_data: VectorData) -> VectorData { let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap(); let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1])); result.style = vector_data.style.clone(); @@ -431,23 +293,7 @@ async fn bounding_box( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn offset_path( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, - distance: f64, - line_join: LineJoin, - #[default(4.)] miter_limit: f64, -) -> VectorData { - let vector_data = vector_data.eval(footprint).await; - +async fn offset_path(_: impl Ctx, vector_data: VectorData, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorData { let subpaths = vector_data.stroke_bezier_paths(); let mut result = VectorData::empty(); result.style = vector_data.style.clone(); @@ -475,20 +321,7 @@ async fn offset_path( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn solidify_stroke( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, -) -> VectorData { - let vector_data = vector_data.eval(footprint).await; - +async fn solidify_stroke(_: impl Ctx, vector_data: VectorData) -> VectorData { let VectorData { transform, style, .. } = &vector_data; let subpaths = vector_data.stroke_bezier_paths(); let mut result = VectorData::empty(); @@ -534,19 +367,7 @@ async fn solidify_stroke( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn flatten_vector_elements( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroup, - Footprint -> GraphicGroup, - )] - graphic_group_input: impl Node, -) -> VectorData { - let graphic_group = graphic_group_input.eval(footprint).await; +async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroup) -> VectorData { // A node based solution to support passing through vector data could be a network node with a cache node connected to // a flatten vector elements connected to an if else node, another connection from the cache directly // To the if else node, and another connection from the cache to a matches type node connected to the if else node. @@ -565,7 +386,7 @@ async fn flatten_vector_elements( } let mut result = VectorData::empty(); - concat_group(&graphic_group, DAffine2::IDENTITY, &mut result); + concat_group(&graphic_group_input, DAffine2::IDENTITY, &mut result); // TODO: This leads to incorrect stroke widths when flattening groups with different transforms. result.style.set_stroke_transform(DAffine2::IDENTITY); @@ -588,31 +409,7 @@ impl ConcatElement for GraphicGroup { } #[node_macro::node(category(""))] -async fn sample_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, - spacing: f64, - start_offset: f64, - stop_offset: f64, - adaptive_spacing: bool, - #[implementations( - () -> Vec, - Footprint -> Vec, - )] - subpath_segment_lengths: impl Node>, -) -> VectorData { - // Evaluate vector data and subpath segment lengths asynchronously. - let vector_data = vector_data.eval(footprint).await; - let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await; - +async fn sample_points(_: impl Ctx, vector_data: VectorData, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec) -> VectorData { // Create an iterator over the bezier segments with enumeration and peeking capability. let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable(); @@ -756,24 +553,14 @@ async fn sample_points( } #[node_macro::node(category(""), path(graphene_core::vector))] -async fn poisson_disk_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, +async fn poisson_disk_points( + _: impl Ctx, + vector_data: VectorData, #[default(10.)] #[min(0.01)] separation_disk_diameter: f64, seed: SeedValue, ) -> VectorData { - let vector_data = vector_data.eval(footprint).await; - let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut result = VectorData::empty(); @@ -815,20 +602,7 @@ async fn poisson_disk_points( } #[node_macro::node(category(""))] -async fn subpath_segment_lengths( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, -) -> Vec { - let vector_data = vector_data.eval(footprint).await; - +async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorData) -> Vec { vector_data .segment_bezier_iter() .map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data.transform.transform_point2(point)).length(None)) @@ -836,21 +610,7 @@ async fn subpath_segment_lengths( } #[node_macro::node(name("Splines from Points"), category("Vector"), path(graphene_core::vector))] -async fn splines_from_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, -) -> VectorData { - // Evaluate the vector data within the given footprint. - let mut vector_data = vector_data.eval(footprint).await; - +async fn splines_from_points(_: impl Ctx, mut vector_data: VectorData) -> VectorData { // Exit early if there are no points to generate splines from. if vector_data.point_domain.positions().is_empty() { return vector_data; @@ -890,22 +650,7 @@ async fn splines_from_points( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn jitter_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - vector_data: impl Node, - #[default(5.)] amount: f64, - seed: SeedValue, -) -> VectorData { - let mut vector_data = vector_data.eval(footprint).await; - +async fn jitter_points(_: impl Ctx, mut vector_data: VectorData, #[default(5.)] amount: f64, seed: SeedValue) -> VectorData { let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let deltas = (0..vector_data.point_domain.positions().len()) @@ -952,30 +697,15 @@ async fn jitter_points( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn morph( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - source: impl Node, - #[expose] - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - target: impl Node, +async fn morph( + _: impl Ctx, + source: VectorData, + #[expose] target: VectorData, #[range((0., 1.))] #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount, ) -> VectorData { - let source = source.eval(footprint).await; - let target = target.eval(footprint).await; let mut result = VectorData::empty(); // Lerp styles @@ -1163,20 +893,8 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn bevel( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - Footprint -> VectorData, - )] - source: impl Node, - #[default(10.)] distance: Length, -) -> VectorData { - bevel_algorithm(source.eval(footprint).await, distance) +fn bevel(_: impl Ctx, source: VectorData, #[default(10.)] distance: Length) -> VectorData { + bevel_algorithm(source, distance) } #[node_macro::node(category("Vector"), path(graphene_core::vector))] @@ -1192,8 +910,9 @@ async fn area(_: (), vector_data: impl Node) -> } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn centroid(_: (), vector_data: impl Node, centroid_type: CentroidType) -> DVec2 { - let vector_data = vector_data.eval(Footprint::default()).await; +async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node, Output = VectorData>, centroid_type: CentroidType) -> DVec2 { + let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); + let vector_data = vector_data.eval(new_ctx).await; if centroid_type == CentroidType::Area { let mut area = 0.; From b8838f65884f91ae6f9157efe52d93270e237689 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 28 Jan 2025 20:39:05 -0800 Subject: [PATCH 10/34] Partial progress (the stuff I'm more sure about) --- node-graph/gcore/src/logic.rs | 12 ++++----- node-graph/gcore/src/transform.rs | 13 ++++------ .../gcore/src/vector/generator_nodes.rs | 26 +++++++++---------- .../src/vector/vector_data/modification.rs | 4 +-- node-graph/gcore/src/vector/vector_nodes.rs | 5 ++-- node-graph/gstd/src/brush.rs | 6 ++--- node-graph/gstd/src/gpu_nodes.rs | 4 +-- node-graph/gstd/src/http.rs | 6 +++-- node-graph/gstd/src/image_color_palette.rs | 2 +- node-graph/gstd/src/text.rs | 3 ++- node-graph/gstd/src/vector.rs | 2 +- node-graph/gstd/src/wasm_application_io.rs | 13 +++++----- 12 files changed, 47 insertions(+), 49 deletions(-) diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 41e182c42e..f229540a6b 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -17,24 +17,24 @@ fn to_string(_: impl Ctx, #[implementations(String, bool, f } #[node_macro::node(category("Debug"))] -async fn switch( - #[implementations(Context)] footprint: F, +async fn switch( + #[implementations(Context)] ctx: C, condition: bool, #[expose] #[implementations( Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2, )] - if_true: impl Node, + if_true: impl Node, #[expose] #[implementations( Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2, )] - if_false: impl Node, + if_false: impl Node, ) -> T { if condition { // we can't remove these calls because we only want to evaluate the brach that we actually need - if_true.eval(footprint).await + if_true.eval(ctx).await } else { - if_false.eval(footprint).await + if_false.eval(ctx).await } } diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 2a120cbee0..c3e5b95ac4 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -3,8 +3,7 @@ use crate::raster::bbox::AxisAlignedBbox; use crate::raster::image::{ImageFrame, ImageFrameTable}; use crate::raster::Pixel; use crate::vector::{VectorData, VectorDataTable}; -use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, ContextImpl, Ctx, ExtractAll, ExtractFootprint, GraphicElement, GraphicGroupTable}; -use crate::{Context, OwnedContextImpl}; +use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl}; use glam::{DAffine2, DVec2}; @@ -303,7 +302,7 @@ impl ApplyTransform for () { #[node_macro::node(category(""))] async fn transform( - input: impl ExtractAll + CloneVarArgs + Ctx, + ctx: impl Ctx + CloneVarArgs + ExtractAll, #[implementations( Context -> VectorDataTable, Context -> GraphicGroupTable, @@ -316,16 +315,14 @@ async fn transform( scale: DVec2, shear: DVec2, _pivot: DVec2, -) -> T -where -{ +) -> T { let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]); - let mut footprint = *input.try_footprint().unwrap(); + let mut footprint = *ctx.try_footprint().unwrap(); if !footprint.ignore_modifications { footprint.apply_transform(&modification); } - let mut ctx = OwnedContextImpl::from(input); + let mut ctx = OwnedContextImpl::from(ctx); ctx.set_footprint(footprint); let mut transform_target = transform_target.eval(Some(ctx.into())).await; diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 89aedc02ea..5798e7c575 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,5 +1,5 @@ -use crate::transform::Footprint; use crate::vector::{HandleId, PointId, VectorData, VectorDataTable}; +use crate::Ctx; use bezier_rs::Subpath; use glam::DVec2; @@ -35,12 +35,12 @@ impl CornerRadius for [f64; 4] { } #[node_macro::node(category("Vector: Shape"))] -fn circle(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable { +fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable { VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) } #[node_macro::node(category("Vector: Shape"))] -fn ellipse(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { +fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { let radius = DVec2::new(radius_x, radius_y); let corner1 = -radius; let corner2 = radius; @@ -58,8 +58,8 @@ fn ellipse(#[implementations((), Footprint)] _footprint: F, _prima } #[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))] -fn rectangle( - #[implementations((), Footprint)] _footprint: F, +fn rectangle( + _: impl Ctx, _primary: (), #[default(100)] width: f64, #[default(100)] height: f64, @@ -71,8 +71,8 @@ fn rectangle( } #[node_macro::node(category("Vector: Shape"))] -fn regular_polygon( - #[implementations((), Footprint)] _footprint: F, +fn regular_polygon( + _: impl Ctx, _primary: (), #[default(6)] #[min(3.)] @@ -85,8 +85,8 @@ fn regular_polygon( } #[node_macro::node(category("Vector: Shape"))] -fn star( - #[implementations((), Footprint)] _footprint: F, +fn star( + _: impl Ctx, _primary: (), #[default(5)] #[min(2.)] @@ -102,14 +102,13 @@ fn star( } #[node_macro::node(category("Vector: Shape"))] -fn line(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable { +fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable { VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end))) } #[node_macro::node(category("Vector: Shape"))] -fn spline(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec) -> VectorDataTable { +fn spline(_: impl Ctx, _primary: (), points: Vec) -> VectorDataTable { let mut spline = VectorData::from_subpath(Subpath::new_cubic_spline(points)); - for pair in spline.segment_domain.ids().windows(2) { spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]); } @@ -119,9 +118,8 @@ fn spline(#[implementations((), Footprint)] _footprint: F, _primar // TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node #[node_macro::node(category(""))] -fn path(#[implementations((), Footprint)] _footprint: F, path_data: Vec>, colinear_manipulators: Vec) -> VectorDataTable { +fn path(_: impl Ctx, path_data: Vec>, colinear_manipulators: Vec) -> VectorDataTable { let mut vector_data = VectorData::from_subpaths(path_data, false); - vector_data.colinear_manipulators = colinear_manipulators .iter() .filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data)) diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 4a637477d9..85356e19bb 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -437,10 +437,10 @@ impl core::hash::Hash for VectorModification { // vector_data: impl Node, // modification: Box, // ) -> VectorDataTable { -// let mut vector_data = vector_data.eval(input).await; // let vector_data = vector_data.one_item_mut(); -// modification.apply(vector_data); +// let mut vector_data = vector_data.eval(input).await; +// modification.apply(&mut vector_data); // VectorDataTable::new(vector_data.clone()) // } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 64130e4687..f74a7ab4a6 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -954,8 +954,9 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn area(_: (), vector_data: impl Node) -> f64 { - let vector_data = vector_data.eval(Footprint::default()).await; +async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node, Output = VectorDataTable>) -> f64 { + let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); + let vector_data = vector_data.eval(new_ctx).await; let vector_data = vector_data.one_item(); let mut area = 0.; diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 40876dd1ce..751a909560 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -10,12 +10,12 @@ use graphene_core::transform::{Footprint, Transform, TransformMut}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; use graphene_core::vector::VectorDataTable; -use graphene_core::Node; +use graphene_core::{Ctx, Node}; use glam::{DAffine2, DVec2}; #[node_macro::node(category("Debug"))] -fn vector_points(_: (), vector_data: VectorDataTable) -> Vec { +fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec { let vector_data = vector_data.one_item(); vector_data.point_domain.positions().to_vec() @@ -202,7 +202,7 @@ pub fn blend_with_mode(background: ImageFrame, foreground: ImageFrame, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { +fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { let image = image.one_item().clone(); let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); diff --git a/node-graph/gstd/src/gpu_nodes.rs b/node-graph/gstd/src/gpu_nodes.rs index c30ddf015d..4e2233f33d 100644 --- a/node-graph/gstd/src/gpu_nodes.rs +++ b/node-graph/gstd/src/gpu_nodes.rs @@ -19,7 +19,7 @@ use crate::wasm_application_io::WasmApplicationIo; // TODO: Move to graph-craft #[node_macro::node(category("Debug: GPU"))] -async fn compile_gpu<'a: 'n>(_: (), node: &'a DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result { +async fn compile_gpu<'a: 'n>(_: impl Ctx, node: &'a DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result { let mut typing_context = typing_context; let compiler = graph_craft::graphene_compiler::Compiler {}; let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() }; @@ -279,7 +279,7 @@ where } #[node_macro::node(category("Debug: GPU"))] -async fn blend_gpu_image(_: (), foreground: ImageFrameTable, background: ImageFrameTable, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable { +async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable, background: ImageFrameTable, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable { let foreground = foreground.one_item(); let background = background.one_item(); diff --git a/node-graph/gstd/src/http.rs b/node-graph/gstd/src/http.rs index 26c6fab0f0..a90af0ba97 100644 --- a/node-graph/gstd/src/http.rs +++ b/node-graph/gstd/src/http.rs @@ -1,9 +1,11 @@ +use graphene_core::Ctx; + #[node_macro::node(category("Network"))] -async fn get_request(_: (), url: String) -> reqwest::Response { +async fn get_request(_: impl Ctx, url: String) -> reqwest::Response { reqwest::get(url).await.unwrap() } #[node_macro::node(category("Network"))] -async fn post_request(_: (), url: String, body: String) -> reqwest::Response { +async fn post_request(_: impl Ctx, url: String, body: String) -> reqwest::Response { reqwest::Client::new().post(url).body(body).send().await.unwrap() } diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index 03ac5245f6..c46d58c670 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -1,6 +1,6 @@ use graphene_core::raster::image::ImageFrameTable; use graphene_core::transform::Footprint; -use graphene_core::Color; +use graphene_core::{Color, Ctx}; #[node_macro::node(category("Raster"))] async fn image_color_palette( diff --git a/node-graph/gstd/src/text.rs b/node-graph/gstd/src/text.rs index 11a7b49bbb..3139307cca 100644 --- a/node-graph/gstd/src/text.rs +++ b/node-graph/gstd/src/text.rs @@ -3,10 +3,11 @@ use crate::vector::{VectorData, VectorDataTable}; use graph_craft::wasm_application_io::WasmEditorApi; use graphene_core::text::TypesettingConfig; pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache}; +use graphene_core::Ctx; #[node_macro::node(category(""))] fn text<'i: 'n>( - _: (), + _: impl Ctx, editor: &'i WasmEditorApi, text: String, font_name: Font, diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index 852e498b6b..a6502bd814 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -5,7 +5,7 @@ use graphene_core::vector::misc::BooleanOperation; use graphene_core::vector::style::Fill; pub use graphene_core::vector::*; use graphene_core::{transform::Transform, GraphicGroup}; -use graphene_core::{Color, GraphicElement, GraphicGroupTable}; +use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable}; pub use path_bool as path_bool_lib; use path_bool::{FillRule, PathBooleanOperation}; diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index bdbedda9e0..4714e59d53 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -12,8 +12,7 @@ use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::transform::Footprint; use graphene_core::vector::VectorDataTable; -use graphene_core::GraphicGroupTable; -use graphene_core::{Color, WasmNotSend}; +use graphene_core::{Color, Ctx, GraphicGroupTable, WasmNotSend}; #[cfg(target_arch = "wasm32")] use base64::Engine; @@ -27,7 +26,7 @@ use wasm_bindgen::JsCast; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; #[node_macro::node(category("Debug: GPU"))] -async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc { +async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc { Arc::new(editor.application_io.as_ref().unwrap().create_window()) } @@ -37,7 +36,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc, // surface_handle: Arc, // ) -> graphene_core::application_io::SurfaceHandleFrame { @@ -60,7 +59,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> { +async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> { let Some(api) = editor.application_io.as_ref() else { return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); }; @@ -75,7 +74,7 @@ async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] edito } #[node_macro::node(category("Raster"))] -fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrameTable { +fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable { let Some(image) = image::load_from_memory(data.as_ref()).ok() else { return ImageFrameTable::default(); }; @@ -151,7 +150,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen #[node_macro::node(category(""))] #[cfg(target_arch = "wasm32")] async fn rasterize( - _: (), + _: impl Ctx, #[implementations( Footprint -> VectorDataTable, Footprint -> ImageFrameTable, From 9dee8cbebd7735cc073f3727c3ba34fabf957a38 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 28 Jan 2025 20:39:14 -0800 Subject: [PATCH 11/34] Partial progress (the stuff that's not compiling and I'm not sure about) --- .../src/vector/vector_data/modification.rs | 17 +++-------------- node-graph/gcore/src/vector/vector_nodes.rs | 2 +- node-graph/gstd/src/dehaze.rs | 17 ++--------------- node-graph/gstd/src/image_color_palette.rs | 15 +++------------ node-graph/gstd/src/vector.rs | 19 +++---------------- 5 files changed, 12 insertions(+), 58 deletions(-) diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 85356e19bb..8e124b654e 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -1,6 +1,7 @@ use super::*; use crate::transform::Footprint; use crate::uuid::generate_uuid; +use crate::Ctx; use bezier_rs::BezierHandles; use dyn_any::DynAny; @@ -424,22 +425,10 @@ impl core::hash::Hash for VectorModification { /// A node that applies a procedural modification to some [`VectorData`]. // #[node_macro::node(category(""))] -// async fn path_modify( -// #[implementations( -// (), -// Footprint, -// )] -// input: F, -// #[implementations( -// () -> VectorDataTable, -// Footprint -> VectorDataTable, -// )] -// vector_data: impl Node, -// modification: Box, -// ) -> VectorDataTable { +// async fn path_modify(_ctx: impl Ctx, vector_data: VectorDataTable, modification: Box) -> VectorDataTable { // let vector_data = vector_data.one_item_mut(); -// let mut vector_data = vector_data.eval(input).await; +// let mut vector_data = vector_data; // modification.apply(&mut vector_data); // VectorDataTable::new(vector_data.clone()) diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index f74a7ab4a6..b228909e86 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -236,7 +236,7 @@ async fn circular_repeat( +async fn copy_to_points( _: impl Ctx, points: VectorDataTable, #[expose] diff --git a/node-graph/gstd/src/dehaze.rs b/node-graph/gstd/src/dehaze.rs index 647983104e..d62843deb0 100644 --- a/node-graph/gstd/src/dehaze.rs +++ b/node-graph/gstd/src/dehaze.rs @@ -2,27 +2,14 @@ use graph_craft::proto::types::Percentage; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::Image; use graphene_core::transform::Footprint; -use graphene_core::Color; +use graphene_core::{Color, Ctx}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; use ndarray::{Array2, ArrayBase, Dim, OwnedRepr}; use std::cmp::{max, min}; #[node_macro::node(category("Raster: Filter"))] -async fn dehaze( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> ImageFrameTable, - Footprint -> ImageFrameTable, - )] - image_frame: impl Node>, - strength: Percentage, -) -> ImageFrameTable { - let image_frame = image_frame.eval(footprint).await; +async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable, strength: Percentage) -> ImageFrameTable { let image_frame = image_frame.one_item(); // Prepare the image data for processing diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index c46d58c670..0ceb6ba905 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -3,17 +3,9 @@ use graphene_core::transform::Footprint; use graphene_core::{Color, Ctx}; #[node_macro::node(category("Raster"))] -async fn image_color_palette( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> ImageFrameTable, - Footprint -> ImageFrameTable, - )] - image: impl Node>, +async fn image_color_palette( + _: impl Ctx, + image: ImageFrameTable, #[min(1.)] #[max(28.)] max_size: u32, @@ -25,7 +17,6 @@ async fn image_color_palette( let mut histogram: Vec = vec![0; (bins + 1.) as usize]; let mut colors: Vec> = vec![vec![]; (bins + 1.) as usize]; - let image = image.eval(footprint).await; let image = image.one_item(); for pixel in image.image.data.iter() { diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index a6502bd814..510b8d72a5 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -13,23 +13,10 @@ use glam::{DAffine2, DVec2}; use std::ops::Mul; #[node_macro::node(category(""))] -async fn boolean_operation( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - )] - group_of_paths: impl Node, - operation: BooleanOperation, -) -> VectorDataTable { - let group_of_paths = group_of_paths.eval(footprint).await; - let group_of_paths = group_of_paths.one_item(); - +async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable { fn vector_from_image(image_frame: T) -> VectorData { + let group_of_paths = group_of_paths.one_item(); + let corner1 = DVec2::ZERO; let corner2 = DVec2::new(1., 1.); From 2a16b81be936b08be9f0abb6f131658d61802c12 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 29 Jan 2025 10:43:21 +0100 Subject: [PATCH 12/34] Fix more errors --- node-graph/gcore/src/raster/adjustments.rs | 2 +- node-graph/gcore/src/value.rs | 8 +- .../src/vector/vector_data/attributes.rs | 76 +++++++++---------- .../src/vector/vector_data/modification.rs | 14 ++-- node-graph/gstd/src/brush.rs | 3 +- node-graph/gstd/src/raster.rs | 2 +- 6 files changed, 52 insertions(+), 53 deletions(-) diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 05f830d5c3..52578232dd 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -665,7 +665,7 @@ async fn blend + Send>( } #[node_macro::node(category(""))] -async fn blend_color_pair(input: (Color, Color), blend_mode: impl Node, Output = BlendMode>, opacity: impl Node, Output = Percentage>) -> Color { +async fn blend_color_pair(input: (Color, Color), blend_mode: impl Node, Output = BlendMode>, opacity: impl Node, Output = Percentage>) -> Color { let blend_mode = blend_mode.eval(None).await; let opacity = opacity.eval(None).await; blend_colors(input.0, input.1, blend_mode, opacity / 100.) diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 047f55b55c..fd63934b65 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -19,10 +19,10 @@ impl<'i, const N: u32> Node<'i, ()> for IntNode { #[derive(Default, Debug, Clone, Copy)] pub struct ValueNode(pub T); -impl<'i, T: 'i> Node<'i, ()> for ValueNode { +impl<'i, T: 'i, I> Node<'i, I> for ValueNode { type Output = &'i T; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { &self.0 } } @@ -140,10 +140,10 @@ impl DebugClonedNode { #[derive(Clone, Copy)] pub struct CopiedNode(pub T); -impl<'i, T: Copy + 'i> Node<'i, ()> for CopiedNode { +impl<'i, T: Copy + 'i, I> Node<'i, I> for CopiedNode { type Output = T; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { self.0 } } diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index 0da4a05025..cb9b7b1db2 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -775,44 +775,44 @@ impl bezier_rs::Identifier for PointId { } } -// impl ConcatElement for VectorData { -// fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { -// let new_ids = other -// .point_domain -// .id -// .iter() -// .filter(|id| self.point_domain.id.contains(id)) -// .map(|&old| (old, old.generate_from_hash(node_id))); -// let point_map = new_ids.collect::>(); -// let new_ids = other -// .segment_domain -// .ids -// .iter() -// .filter(|id| self.segment_domain.ids.contains(id)) -// .map(|&old| (old, old.generate_from_hash(node_id))); -// let segment_map = new_ids.collect::>(); -// let new_ids = other -// .region_domain -// .ids -// .iter() -// .filter(|id| self.region_domain.ids.contains(id)) -// .map(|&old| (old, old.generate_from_hash(node_id))); -// let region_map = new_ids.collect::>(); -// let id_map = IdMap { -// point_offset: self.point_domain.ids().len(), -// point_map, -// segment_map, -// region_map, -// }; -// self.point_domain.concat(&other.point_domain, transform * other.transform, &id_map); -// self.segment_domain.concat(&other.segment_domain, transform * other.transform, &id_map); -// self.region_domain.concat(&other.region_domain, transform * other.transform, &id_map); -// // TODO: properly deal with fills such as gradients -// self.style = other.style.clone(); -// self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied()); -// self.alpha_blending = other.alpha_blending; -// } -// } +impl ConcatElement for VectorData { + fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { + let new_ids = other + .point_domain + .id + .iter() + .filter(|id| self.point_domain.id.contains(id)) + .map(|&old| (old, old.generate_from_hash(node_id))); + let point_map = new_ids.collect::>(); + let new_ids = other + .segment_domain + .ids + .iter() + .filter(|id| self.segment_domain.ids.contains(id)) + .map(|&old| (old, old.generate_from_hash(node_id))); + let segment_map = new_ids.collect::>(); + let new_ids = other + .region_domain + .ids + .iter() + .filter(|id| self.region_domain.ids.contains(id)) + .map(|&old| (old, old.generate_from_hash(node_id))); + let region_map = new_ids.collect::>(); + let id_map = IdMap { + point_offset: self.point_domain.ids().len(), + point_map, + segment_map, + region_map, + }; + self.point_domain.concat(&other.point_domain, transform * other.transform, &id_map); + self.segment_domain.concat(&other.segment_domain, transform * other.transform, &id_map); + self.region_domain.concat(&other.region_domain, transform * other.transform, &id_map); + // TODO: properly deal with fills such as gradients + self.style = other.style.clone(); + self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied()); + self.alpha_blending = other.alpha_blending; + } +} impl ConcatElement for VectorDataTable { fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) { diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 8e124b654e..09d4d31314 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -424,15 +424,15 @@ impl core::hash::Hash for VectorModification { } /// A node that applies a procedural modification to some [`VectorData`]. -// #[node_macro::node(category(""))] -// async fn path_modify(_ctx: impl Ctx, vector_data: VectorDataTable, modification: Box) -> VectorDataTable { -// let vector_data = vector_data.one_item_mut(); +#[node_macro::node(category(""))] +async fn path_modify(_ctx: impl Ctx, vector_data: VectorDataTable, modification: Box) -> VectorDataTable { + let vector_data = vector_data.one_item_mut(); -// let mut vector_data = vector_data; -// modification.apply(&mut vector_data); + let mut vector_data = vector_data; + modification.apply(&mut vector_data); -// VectorDataTable::new(vector_data.clone()) -// } + VectorDataTable::new(vector_data.clone()) +} #[test] fn modify_new() { diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 751a909560..6ed871f672 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -132,8 +132,7 @@ where } pub fn create_brush_texture(brush_style: &BrushStyle) -> Image { - let stamp = BrushStampGeneratorNode::new(CopiedNode::new(brush_style.color), CopiedNode::new(brush_style.hardness), CopiedNode::new(brush_style.flow)); - let stamp = stamp.eval(brush_style.diameter); + let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); let blank_texture = EmptyImageNode::new(CopiedNode::new(transform), CopiedNode::new(Color::TRANSPARENT)).eval(()); let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)); diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index daabb05a3c..c4cc8e0049 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -435,7 +435,7 @@ fn empty_image(_: impl Ctx, transform: DAffine2, #[implementations(Col #[node_macro::node(category("Raster: Generator"))] #[allow(clippy::too_many_arguments)] fn noise_pattern( - ctx: impl ExtractFootprint, + ctx: impl ExtractFootprint + Ctx, _primary: (), clip: bool, seed: u32, From 83a15e2b14b78bab158c75e75a16c9b5e6f5d861 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 29 Jan 2025 11:02:32 +0100 Subject: [PATCH 13/34] First pass of fixing errors introduced by rebase --- node-graph/gcore/src/graphic_element.rs | 14 ++----- node-graph/gcore/src/logic.rs | 2 +- node-graph/gcore/src/raster/adjustments.rs | 4 +- node-graph/gcore/src/transform.rs | 2 +- .../src/vector/vector_data/modification.rs | 5 +-- node-graph/gcore/src/vector/vector_nodes.rs | 6 +-- node-graph/wgpu-executor/src/lib.rs | 42 ++++++++----------- 7 files changed, 31 insertions(+), 44 deletions(-) diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 9b746a70ba..6f8804fecd 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -5,8 +5,7 @@ use crate::raster::BlendMode; use crate::transform::{Transform, TransformMut}; use crate::uuid::NodeId; use crate::vector::{VectorData, VectorDataTable}; -use crate::{CloneVarArgs, Color, Ctx, ExtractAll, OwnedContextImpl}; -use crate::{Color, Context}; +use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use dyn_any::DynAny; @@ -281,11 +280,8 @@ impl ArtboardGroup { } #[node_macro::node(category(""))] -async fn layer(_: impl Ctx, mut stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec) -> GraphicGroupTable { - let mut element = element.eval(footprint).await; - let stack = stack.eval(footprint).await; - let stack = stack.one_item(); - let mut stack = stack.clone(); +async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec) -> GraphicGroupTable { + let mut stack = stack.one_item().clone(); if stack.transform.matrix2.determinant() != 0. { *element.transform_mut() = stack.transform.inverse() * element.transform(); @@ -331,9 +327,7 @@ async fn to_group + 'n>( #[node_macro::node(category("General"))] async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable { - let nested_group = group.eval(footprint).await; - let nested_group = nested_group.one_item(); - let nested_group = nested_group.clone(); + let nested_group = group.one_item().clone(); let mut flat_group = GraphicGroup::default(); diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index f229540a6b..494a473596 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -12,7 +12,7 @@ fn log_to_console(_: impl Ctx, #[implementations(String, bo } #[node_macro::node(category("Debug"))] -fn to_string(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorData, DAffine2)] value: T) -> String { +fn to_string(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String { format!("{:?}", value) } diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 52578232dd..718a6730e9 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -647,14 +647,14 @@ async fn blend + Send>( _: impl Ctx, #[implementations( Color, - ImageFrameTableTable, + ImageFrameTable, GradientStops, )] over: T, #[expose] #[implementations( Color, - ImageFrame, + ImageFrameTable, GradientStops, )] under: T, diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index c3e5b95ac4..76eca5ae63 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -3,7 +3,7 @@ use crate::raster::bbox::AxisAlignedBbox; use crate::raster::image::{ImageFrame, ImageFrameTable}; use crate::raster::Pixel; use crate::vector::{VectorData, VectorDataTable}; -use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl}; +use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl}; use glam::{DAffine2, DVec2}; diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 09d4d31314..260a9bfb52 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -425,10 +425,9 @@ impl core::hash::Hash for VectorModification { /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] -async fn path_modify(_ctx: impl Ctx, vector_data: VectorDataTable, modification: Box) -> VectorDataTable { - let vector_data = vector_data.one_item_mut(); +async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box) -> VectorDataTable { + let mut vector_data = vector_data.one_item_mut(); - let mut vector_data = vector_data; modification.apply(&mut vector_data); VectorDataTable::new(vector_data.clone()) diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index b228909e86..ae4d0287e3 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -305,7 +305,7 @@ async fn copy_to_points( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorData { +async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { let vector_data = vector_data.one_item(); let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap(); @@ -601,7 +601,7 @@ async fn poisson_disk_points( #[min(0.01)] separation_disk_diameter: f64, seed: SeedValue, -) -> VectorData { +) -> VectorDataTable { let vector_data = vector_data.one_item(); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); @@ -754,7 +754,7 @@ async fn morph( #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount, -) -> VectorData { +) -> VectorDataTable { let source = source.one_item(); let target = target.one_item(); diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 712103906c..a0a08901e6 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -6,9 +6,11 @@ pub use executor::GpuExecutor; use dyn_any::{DynAny, StaticType}; use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer}; -use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; +use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle, TextureFrame}; +use graphene_core::raster::image::ImageFrameTable; +use graphene_core::raster::{Image, SRGBA8}; use graphene_core::transform::{Footprint, Transform}; -use graphene_core::{Color, Cow, ExtractFootprint, Node, OwnedContextImpl, SurfaceFrame, Ctx, Type}; +use graphene_core::{Color, Cow, Ctx, ExtractFootprint, Node, OwnedContextImpl, SurfaceFrame, Type}; use anyhow::{bail, Result}; use futures::Future; @@ -910,18 +912,10 @@ async fn render_texture<'a: 'n>( } #[node_macro::node(category(""))] -async fn upload_texture<'a: 'n>( - footprint: impl ExtractFootprint + Ctx, - #[implementations(graphene_core::Context -> ImageFrameTable)] input: impl Node, Output = ImageFrameTable>, - executor: &'a WgpuExecutor, -) -> TextureFrame { +async fn upload_texture<'a: 'n>(footprint: impl ExtractFootprint + Ctx, input: ImageFrameTable, executor: &'a WgpuExecutor) -> TextureFrame { // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); - let footprint = footprint.try_footprint().copied(); - let ctx = OwnedContextImpl { footprint, ..Default::default() }; - let input = input.one_item(); - let input = input.eval(Some(ctx.into())).await; let new_data: Vec = input.image.data.iter().map(|x| (*x).into()).collect(); let new_image = Image { width: input.image.width, @@ -930,16 +924,16 @@ async fn upload_texture<'a: 'n>( base64_string: None, }; -// let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap(); -// let texture = match shader_input { -// ShaderInput::TextureBuffer(buffer, _) => buffer, -// ShaderInput::StorageTextureBuffer(buffer, _) => buffer, -// _ => unreachable!("Unsupported ShaderInput type"), -// }; - -// TextureFrame { -// texture: texture.into(), -// transform: input.transform, -// alpha_blend: Default::default(), -// } -// } + let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap(); + let texture = match shader_input { + ShaderInput::TextureBuffer(buffer, _) => buffer, + ShaderInput::StorageTextureBuffer(buffer, _) => buffer, + _ => unreachable!("Unsupported ShaderInput type"), + }; + + TextureFrame { + texture: texture.into(), + transform: input.transform, + alpha_blend: Default::default(), + } +} From 5a3aaa1b0c9a268e3612df8d4df06583c5bfe2c6 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 29 Jan 2025 12:00:01 +0100 Subject: [PATCH 14/34] Port wasm application io --- node-graph/gcore/src/raster/adjustments.rs | 6 +-- node-graph/gstd/src/brush.rs | 1 + node-graph/gstd/src/vector.rs | 5 +-- node-graph/gstd/src/wasm_application_io.rs | 46 ++++++++++++---------- node-graph/wgpu-executor/src/lib.rs | 2 +- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 718a6730e9..2064cf53d7 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -665,9 +665,9 @@ async fn blend + Send>( } #[node_macro::node(category(""))] -async fn blend_color_pair(input: (Color, Color), blend_mode: impl Node, Output = BlendMode>, opacity: impl Node, Output = Percentage>) -> Color { - let blend_mode = blend_mode.eval(None).await; - let opacity = opacity.eval(None).await; +async fn blend_color_pair(input: (Color, Color), blend_mode: impl Node<(), Output = BlendMode>, opacity: impl Node<(), Output = Percentage>) -> Color { + let blend_mode = blend_mode.eval(()).await; + let opacity = opacity.eval(()).await; blend_colors(input.0, input.1, blend_mode, opacity / 100.) } diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 6ed871f672..64116a7c19 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -136,6 +136,7 @@ pub fn create_brush_texture(brush_style: &BrushStyle) -> Image { let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); let blank_texture = EmptyImageNode::new(CopiedNode::new(transform), CopiedNode::new(Color::TRANSPARENT)).eval(()); let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)); + normal_blend.eval((Color::default(), Color::default())); let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend)); blend_executor.eval((blank_texture, stamp)).image } diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index 510b8d72a5..aab0fffa79 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -1,5 +1,3 @@ -use crate::transform::Footprint; - use bezier_rs::{ManipulatorGroup, Subpath}; use graphene_core::vector::misc::BooleanOperation; use graphene_core::vector::style::Fill; @@ -15,8 +13,6 @@ use std::ops::Mul; #[node_macro::node(category(""))] async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable { fn vector_from_image(image_frame: T) -> VectorData { - let group_of_paths = group_of_paths.one_item(); - let corner1 = DVec2::ZERO; let corner2 = DVec2::new(1., 1.); @@ -180,6 +176,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera } } + let group_of_paths = group_of_paths.one_item(); // The first index is the bottom of the stack let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation); diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 4714e59d53..99b6df07e2 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -12,7 +12,7 @@ use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::transform::Footprint; use graphene_core::vector::VectorDataTable; -use graphene_core::{Color, Ctx, GraphicGroupTable, WasmNotSend}; +use graphene_core::{Color, Context, Ctx, GraphicGroupTable, OwnedContextImpl, WasmNotSend}; #[cfg(target_arch = "wasm32")] use base64::Engine; @@ -150,22 +150,24 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen #[node_macro::node(category(""))] #[cfg(target_arch = "wasm32")] async fn rasterize( - _: impl Ctx, + footprint: impl Ctx + ExtractFootprint, #[implementations( - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - Footprint -> GraphicGroupTable, + VectorDataTable, + ImageFrameTable, + GraphicGroupTable, )] - data: impl Node, + data: Output, footprint: Footprint, surface_handle: Arc>, ) -> ImageFrameTable { + use graphene_core::ExtractFootprint; + + let footprint = footprint.footprint(); if footprint.transform.matrix2.determinant() == 0. { log::trace!("Invalid footprint received for rasterization"); return ImageFrameTable::default(); } - let mut data = data.eval(footprint).await; let mut render = SvgRender::new(); let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox(); let size = aabb.size(); @@ -212,29 +214,31 @@ async fn rasterize( render_config: RenderConfig, - editor_api: &'a WasmEditorApi, + editor_api: impl Node, Output = &'a WasmEditorApi>, #[implementations( - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - Footprint -> GraphicGroupTable, - Footprint -> graphene_core::Artboard, - Footprint -> graphene_core::ArtboardGroup, - Footprint -> Option, - Footprint -> Vec, - Footprint -> bool, - Footprint -> f32, - Footprint -> f64, - Footprint -> String, + Context -> VectorDataTable, + Context -> ImageFrameTable, + Context -> GraphicGroupTable, + Context -> graphene_core::Artboard, + Context -> graphene_core::ArtboardGroup, + Context -> Option, + Context -> Vec, + Context -> bool, + Context -> f32, + Context -> f64, + Context -> String, )] - data: impl Node, + data: impl Node, Output = T>, _surface_handle: impl Node<(), Output = Option>, ) -> RenderOutput { let footprint = render_config.viewport; + let ctx = OwnedContextImpl::default().with_footprint(footprint).into_context(); let RenderConfig { hide_artboards, for_export, .. } = render_config; let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); - let data = data.eval(footprint).await; + let data = data.eval(ctx.clone()).await; + let editor_api = editor_api.eval(ctx).await; #[cfg(all(feature = "vello", target_arch = "wasm32"))] let surface_handle = _surface_handle.eval(()).await; let use_vello = editor_api.editor_preferences.use_vello(); diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index a0a08901e6..4b3f3d3f56 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -912,7 +912,7 @@ async fn render_texture<'a: 'n>( } #[node_macro::node(category(""))] -async fn upload_texture<'a: 'n>(footprint: impl ExtractFootprint + Ctx, input: ImageFrameTable, executor: &'a WgpuExecutor) -> TextureFrame { +async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable, executor: &'a WgpuExecutor) -> TextureFrame { // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); let input = input.one_item(); From 7ff8c64a1f18a352f856427686d6307a9585626a Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 29 Jan 2025 12:34:32 +0100 Subject: [PATCH 15/34] Fix brush node types --- node-graph/gcore/src/raster/adjustments.rs | 14 +++++++++----- node-graph/gcore/src/value.rs | 14 +++++++------- node-graph/gstd/src/brush.rs | 12 ++++++------ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 2064cf53d7..7fed8b4624 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -8,7 +8,7 @@ use crate::raster::{Channel, Color, Pixel}; use crate::registry::types::{Angle, Percentage, SignedPercentage}; use crate::vector::style::GradientStops; use crate::vector::VectorDataTable; -use crate::{Context, Ctx}; +use crate::{Context, Ctx, Node}; use crate::{GraphicElement, GraphicGroupTable}; use dyn_any::DynAny; @@ -664,10 +664,14 @@ async fn blend + Send>( over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) } -#[node_macro::node(category(""))] -async fn blend_color_pair(input: (Color, Color), blend_mode: impl Node<(), Output = BlendMode>, opacity: impl Node<(), Output = Percentage>) -> Color { - let blend_mode = blend_mode.eval(()).await; - let opacity = opacity.eval(()).await; +#[node_macro::node(category(""), skip_impl)] +fn blend_color_pair(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color +where + BlendModeNode: Node<'n, (), Output = BlendMode> + 'n, + OpacityNode: Node<'n, (), Output = Percentage> + 'n, +{ + let blend_mode = blend_mode.eval(()); + let opacity = opacity.eval(()); blend_colors(input.0, input.1, blend_mode, opacity / 100.) } diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index fd63934b65..e63ad453cb 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -8,10 +8,10 @@ use core::{ #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct IntNode; -impl<'i, const N: u32> Node<'i, ()> for IntNode { +impl<'i, const N: u32, I> Node<'i, I> for IntNode { type Output = u32; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { N } } @@ -77,10 +77,10 @@ impl RefCellMutNode { #[derive(Default)] pub struct OnceCellNode(pub Cell); -impl<'i, T: Default + 'i> Node<'i, ()> for OnceCellNode { +impl<'i, T: Default + 'i, I> Node<'i, I> for OnceCellNode { type Output = T; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { self.0.replace(T::default()) } } @@ -157,9 +157,9 @@ impl CopiedNode { #[derive(Default)] pub struct DefaultNode(PhantomData); -impl<'i, T: Default + 'i> Node<'i, ()> for DefaultNode { +impl<'i, T: Default + 'i, I> Node<'i, I> for DefaultNode { type Output = T; - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { T::default() } } @@ -205,7 +205,7 @@ mod test { #[test] fn test_default_node() { let node = DefaultNode::::new(); - assert_eq!(node.eval(()), 0); + assert_eq!(node.eval(42), 0); } #[test] #[allow(clippy::unit_cmp)] diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 64116a7c19..60663c578b 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -6,7 +6,7 @@ use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::BlendMode; use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, Pixel, Sample}; -use graphene_core::transform::{Footprint, Transform, TransformMut}; +use graphene_core::transform::{Transform, TransformMut}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; use graphene_core::vector::VectorDataTable; @@ -135,7 +135,7 @@ pub fn create_brush_texture(brush_style: &BrushStyle) -> Image { let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); let blank_texture = EmptyImageNode::new(CopiedNode::new(transform), CopiedNode::new(Color::TRANSPARENT)).eval(()); - let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)); + let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.))); normal_blend.eval((Color::default(), Color::default())); let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend)); blend_executor.eval((blank_texture, stamp)).image @@ -246,7 +246,7 @@ fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTable, bounds: ImageFrameTable { - let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.)); + let blend_params = BlendColorPairNode::new(ValueNode(CopiedNode::new(BlendMode::Erase)), ValueNode(CopiedNode::new(100.))); let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params)); erase_restore_mask = blit_node.eval(erase_restore_mask); } // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. BlendMode::Restore => { - let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.)); + let blend_params = BlendColorPairNode::new(ValueNode(CopiedNode::new(BlendMode::Restore)), ValueNode(CopiedNode::new(100.))); let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params)); erase_restore_mask = blit_node.eval(erase_restore_mask); } @@ -302,7 +302,7 @@ fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTable Date: Wed, 29 Jan 2025 12:39:25 +0100 Subject: [PATCH 16/34] Add type annotation --- node-graph/gstd/src/raster.rs | 2 +- node-graph/interpreted-executor/src/node_registry.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index c4cc8e0049..924c907aa4 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -4,7 +4,7 @@ use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::{ Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample, }; -use graphene_core::transform::{Footprint, Transform}; +use graphene_core::transform::Transform; use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node}; use fastnoise_lite; diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index dda75861fe..c2c9ffcf40 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -15,8 +15,8 @@ use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::application_io::TextureFrame; -use graphene_std::raster::*; use graphene_std::wasm_application_io::*; +use graphene_std::{raster::*, Context}; use graphene_std::{GraphicElement, GraphicGroup}; #[cfg(feature = "gpu")] use wgpu_executor::{ShaderInputFrame, WgpuExecutor}; @@ -374,7 +374,7 @@ fn node_registry() -> HashMap, input: Footprint, fn_params: [Footprint => WgpuSurface]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => Option]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]), - register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), + register_node!(graphene_core::structural::ConsNode<(), _>, input: Image, params: [&str]), ]; let mut map: HashMap> = HashMap::new(); for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() { From 837fef86380583efb4c52b1d5aae7465bcc4a312 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 29 Jan 2025 12:58:35 +0100 Subject: [PATCH 17/34] Fix warnings and wasm compilation --- node-graph/gcore/src/context.rs | 4 ++-- node-graph/gcore/src/raster.rs | 3 +-- node-graph/gcore/src/raster/adjustments.rs | 2 +- node-graph/gcore/src/transform.rs | 2 +- node-graph/gcore/src/vector/vector_data/modification.rs | 5 ++--- node-graph/gcore/src/vector/vector_nodes.rs | 4 ++-- node-graph/gstd/src/dehaze.rs | 1 - node-graph/gstd/src/image_color_palette.rs | 1 - node-graph/gstd/src/wasm_application_io.rs | 7 ++----- node-graph/interpreted-executor/src/node_registry.rs | 2 +- node-graph/node-macro/src/codegen.rs | 2 +- node-graph/wgpu-executor/src/lib.rs | 2 +- 12 files changed, 14 insertions(+), 21 deletions(-) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 1233aac0c4..fb5894cf3c 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -51,7 +51,7 @@ impl ExtractFootprint for () { } } -impl<'n, T: ExtractFootprint + Ctx + Sync + Send> ExtractFootprint for &'n T { +impl ExtractFootprint for &T { fn try_footprint(&self) -> Option<&Footprint> { (*self).try_footprint() } @@ -146,7 +146,7 @@ impl ExtractIndex for ContextImpl<'_> { self.index } } -impl<'a> ExtractVarArgs for ContextImpl<'a> { +impl ExtractVarArgs for ContextImpl<'_> { fn vararg(&self, index: usize) -> Result, VarArgsResult> { let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied() diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 0ebff86ea5..7a0ba8c398 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -1,10 +1,9 @@ pub use self::color::{Color, Luma, SRGBA8}; use crate::raster::image::ImageFrameTable; use crate::registry::types::Percentage; -use crate::transform::Footprint; use crate::vector::VectorDataTable; +use crate::Ctx; use crate::GraphicGroupTable; -use crate::{Ctx, GraphicGroup}; use bytemuck::{Pod, Zeroable}; use core::fmt::Debug; diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 7fed8b4624..80d6053757 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -8,7 +8,7 @@ use crate::raster::{Channel, Color, Pixel}; use crate::registry::types::{Angle, Percentage, SignedPercentage}; use crate::vector::style::GradientStops; use crate::vector::VectorDataTable; -use crate::{Context, Ctx, Node}; +use crate::{Ctx, Node}; use crate::{GraphicElement, GraphicGroupTable}; use dyn_any::DynAny; diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 76eca5ae63..a2dd578349 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -1,4 +1,4 @@ -use crate::application_io::{TextureFrame, TextureFrameTable}; +use crate::application_io::TextureFrameTable; use crate::raster::bbox::AxisAlignedBbox; use crate::raster::image::{ImageFrame, ImageFrameTable}; use crate::raster::Pixel; diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 260a9bfb52..221e41ad35 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -1,5 +1,4 @@ use super::*; -use crate::transform::Footprint; use crate::uuid::generate_uuid; use crate::Ctx; @@ -426,9 +425,9 @@ impl core::hash::Hash for VectorModification { /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box) -> VectorDataTable { - let mut vector_data = vector_data.one_item_mut(); + let vector_data = vector_data.one_item_mut(); - modification.apply(&mut vector_data); + modification.apply(vector_data); VectorDataTable::new(vector_data.clone()) } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index ae4d0287e3..315e0baa5d 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -326,7 +326,7 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l result.style.set_stroke_transform(DAffine2::IDENTITY); // Perform operation on all subpaths in this shape. - for mut subpath in vector_data.stroke_bezier_paths() { + for mut subpath in subpaths { subpath.apply_transform(vector_data.transform); // Taking the existing stroke data and passing it to Bezier-rs to generate new paths. @@ -697,7 +697,7 @@ async fn splines_from_points(_: impl Ctx, mut vector_data: VectorDataTable) -> V } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn jitter_points(_: impl Ctx, mut vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable { +async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable { let mut vector_data = vector_data.one_item().clone(); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); diff --git a/node-graph/gstd/src/dehaze.rs b/node-graph/gstd/src/dehaze.rs index d62843deb0..65af328630 100644 --- a/node-graph/gstd/src/dehaze.rs +++ b/node-graph/gstd/src/dehaze.rs @@ -1,7 +1,6 @@ use graph_craft::proto::types::Percentage; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::Image; -use graphene_core::transform::Footprint; use graphene_core::{Color, Ctx}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index 0ceb6ba905..1f2b040fdf 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -1,5 +1,4 @@ use graphene_core::raster::image::ImageFrameTable; -use graphene_core::transform::Footprint; use graphene_core::{Color, Ctx}; #[node_macro::node(category("Raster"))] diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 99b6df07e2..227b297652 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -150,19 +150,16 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen #[node_macro::node(category(""))] #[cfg(target_arch = "wasm32")] async fn rasterize( - footprint: impl Ctx + ExtractFootprint, + _: impl Ctx, #[implementations( VectorDataTable, ImageFrameTable, GraphicGroupTable, )] - data: Output, + mut data: T, footprint: Footprint, surface_handle: Arc>, ) -> ImageFrameTable { - use graphene_core::ExtractFootprint; - - let footprint = footprint.footprint(); if footprint.transform.matrix2.determinant() == 0. { log::trace!("Invalid footprint received for rasterization"); return ImageFrameTable::default(); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index c2c9ffcf40..d50fe07db6 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -15,8 +15,8 @@ use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::application_io::TextureFrame; +use graphene_std::raster::*; use graphene_std::wasm_application_io::*; -use graphene_std::{raster::*, Context}; use graphene_std::{GraphicElement, GraphicGroup}; #[cfg(feature = "gpu")] use wgpu_executor::{ShaderInputFrame, WgpuExecutor}; diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index fce4800159..fa1a56cf7a 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -179,7 +179,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all"); quote!( - for<'all> #all_lifetime_ty: Send, + for<'all> #all_lifetime_ty: #graphene_core::WasmNotSend, // #name: 'n, #name: #graphene_core::Node<'n, #input_type, Output = #ty> ) diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 4b3f3d3f56..be111736d3 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -10,7 +10,7 @@ use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle, Tex use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::{Image, SRGBA8}; use graphene_core::transform::{Footprint, Transform}; -use graphene_core::{Color, Cow, Ctx, ExtractFootprint, Node, OwnedContextImpl, SurfaceFrame, Type}; +use graphene_core::{Color, Cow, Ctx, ExtractFootprint, Node, SurfaceFrame, Type}; use anyhow::{bail, Result}; use futures::Future; From e780b055d8f984c6ee97971f670117e9669998cf Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 29 Jan 2025 18:42:08 +0100 Subject: [PATCH 18/34] Change types for Document Node definitions --- .../node_graph/document_node_definitions.rs | 46 +++--- node-graph/gcore/src/context.rs | 15 +- node-graph/gcore/src/raster.rs | 9 ++ node-graph/gcore/src/raster/color.rs | 7 +- node-graph/gcore/src/transform.rs | 16 +- node-graph/graph-craft/src/document.rs | 2 +- node-graph/graph-craft/src/proto.rs | 8 +- node-graph/gstd/src/raster.rs | 80 +++++++--- node-graph/gstd/src/wasm_application_io.rs | 4 +- .../interpreted-executor/src/node_registry.rs | 147 ++++-------------- node-graph/interpreted-executor/src/util.rs | 8 +- 11 files changed, 165 insertions(+), 177 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 264ee3c9da..465e50988d 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -293,9 +293,9 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), inputs: vec![ - NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(ArtboardGroup))), 0), + NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroup))), 0), NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], @@ -411,7 +411,7 @@ fn static_nodes() -> Vec { DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")), - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), ..Default::default() }, ] @@ -484,7 +484,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(0), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() @@ -554,7 +554,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() @@ -645,7 +645,7 @@ fn static_nodes() -> Vec { DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), ..Default::default() }, DocumentNode { @@ -724,7 +724,7 @@ fn static_nodes() -> Vec { category: "Raster", node_template: NodeTemplate { document_node: DocumentNode { - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode")), inputs: vec![ NodeInput::value(TaggedValue::Bool(false), false), @@ -1000,7 +1000,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(Vec), 2), NodeInput::network(concrete!(BrushCache), 3), ], - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")), ..Default::default() }] @@ -1062,7 +1062,7 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -1081,7 +1081,7 @@ fn static_nodes() -> Vec { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)], - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -1103,7 +1103,7 @@ fn static_nodes() -> Vec { nodes: vec![DocumentNode { inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 1)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")), - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), ..Default::default() }] .into_iter() @@ -1163,7 +1163,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() @@ -1242,7 +1242,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() @@ -1321,7 +1321,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() @@ -1406,7 +1406,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() @@ -1515,7 +1515,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() @@ -1595,7 +1595,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), ..Default::default() @@ -1665,13 +1665,13 @@ fn static_nodes() -> Vec { exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ DocumentNode { - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")), ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(0), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")), ..Default::default() @@ -1736,7 +1736,7 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), inputs: vec![ NodeInput::network(concrete!(ShaderInputFrame), 0), NodeInput::network(concrete!(Arc), 1), @@ -1975,7 +1975,7 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::LineNode"), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![ NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::DVec2(DVec2::new(0., -50.)), false), @@ -2213,7 +2213,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(DVec2), 4), NodeInput::network(concrete!(DVec2), 5), ], - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::TransformNode")), ..Default::default() }, @@ -2373,7 +2373,7 @@ fn static_nodes() -> Vec { NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::U32(0), false), ], - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index fb5894cf3c..8cd5a85c15 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -8,7 +8,10 @@ pub trait Ctx: Clone + Send {} pub trait ExtractFootprint { fn try_footprint(&self) -> Option<&Footprint>; fn footprint(&self) -> &Footprint { - self.try_footprint().expect("Context did not have a footprint") + self.try_footprint().unwrap_or_else(|| { + log::error!("Context did not have a footprint"); + &const { Footprint::empty() } + }) } } @@ -215,6 +218,16 @@ pub struct OwnedContextImpl { pub time: Option, } +impl core::hash::Hash for OwnedContextImpl { + fn hash(&self, state: &mut H) { + self.footprint.hash(state); + self.index.hash(state); + self.time.map(|x| x.to_bits()).hash(state); + self.parent.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state); + self.varargs.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state); + } +} + impl OwnedContextImpl { pub fn from(value: T) -> Self { let footprint = value.try_footprint().copied(); diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 7a0ba8c398..3e35afbd02 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -182,6 +182,9 @@ pub trait Alpha { } fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self; } +pub trait AlphaMut: Alpha { + fn set_alpha(&mut self, value: Self::AlphaChannel); +} pub trait Depth { type DepthChannel: Channel; @@ -228,6 +231,12 @@ pub trait Bitmap { type Pixel: Pixel; fn width(&self) -> u32; fn height(&self) -> u32; + fn dimensions(&self) -> (u32, u32) { + (self.width(), self.height()) + } + fn dim(&self) -> (u32, u32) { + self.dimensions() + } fn get_pixel(&self, x: u32, y: u32) -> Option; } diff --git a/node-graph/gcore/src/raster/color.rs b/node-graph/gcore/src/raster/color.rs index 336ce114df..9d4d673cd9 100644 --- a/node-graph/gcore/src/raster/color.rs +++ b/node-graph/gcore/src/raster/color.rs @@ -1,5 +1,5 @@ use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float}; -use super::{Alpha, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB}; +use super::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB}; use dyn_any::DynAny; #[cfg(feature = "serde")] @@ -257,6 +257,11 @@ impl RGBMut for Color { self.blue = blue; } } +impl AlphaMut for Color { + fn set_alpha(&mut self, value: Self::AlphaChannel) { + self.alpha = value; + } +} impl Pixel for Color { #[cfg(not(target_arch = "spirv"))] diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index a2dd578349..5a7bd19b58 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -244,6 +244,12 @@ pub struct Footprint { impl Default for Footprint { fn default() -> Self { + Self::empty() + } +} + +impl Footprint { + pub const fn empty() -> Self { Self { transform: DAffine2::IDENTITY, resolution: glam::UVec2::new(1920, 1080), @@ -251,9 +257,6 @@ impl Default for Footprint { ignore_modifications: false, } } -} - -impl Footprint { pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox { let inverse = self.transform.inverse(); let start = inverse.transform_point2((0., 0.).into()); @@ -317,15 +320,14 @@ async fn transform( _pivot: DVec2, ) -> T { let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]); - let mut footprint = *ctx.try_footprint().unwrap(); + let mut footprint = *ctx.footprint(); if !footprint.ignore_modifications { footprint.apply_transform(&modification); } - let mut ctx = OwnedContextImpl::from(ctx); - ctx.set_footprint(footprint); + let mut ctx = OwnedContextImpl::from(ctx).with_footprint(footprint); - let mut transform_target = transform_target.eval(Some(ctx.into())).await; + let mut transform_target = transform_target.eval(ctx.into_context()).await; let data_transform = transform_target.transform_mut(); *data_transform = modification * (*data_transform); diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 5e09b9b35b..840ddd22df 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -304,7 +304,7 @@ impl DocumentNode { match first { NodeInput::Value { tagged_value, .. } => { assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs); - (ProtoNodeInput::None, ConstructionArgs::Value(tagged_value)) + (ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context<'static>)), ConstructionArgs::Value(tagged_value)) } NodeInput::Node { node_id, output_index, lambda } => { assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node"); diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 79d780e8fa..f9f162b6a7 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -162,7 +162,7 @@ impl Default for ProtoNode { #[derive(Debug, PartialEq, Eq, Clone, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ProtoNodeInput { - /// [`ProtoNode`]s do not require any input, e.g. the value node just takes in [`ConstructionArgs`]. + /// This input will be converted to `()` as the call argument. None, /// A ManualComposition input represents an input that opts out of being resolved through the `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated /// result to this node. Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor. @@ -224,7 +224,7 @@ impl ProtoNode { Self { identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"), construction_args: value, - input: ProtoNodeInput::None, + input: ProtoNodeInput::ManualComposition(concrete!(Context)), original_location: OriginalLocation { path: Some(path), inputs_exposed: vec![false; inputs_exposed], @@ -552,7 +552,7 @@ impl core::fmt::Debug for GraphErrorType { GraphErrorType::NoImplementations => write!(f, "No implementations found"), GraphErrorType::NoConstructor => write!(f, "No construct found for node"), GraphErrorType::InvalidImplementations { inputs, error_inputs } => { - let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}", index + 1); + let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}, found: {_found}", index + 1); let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::>().join("\n"); let mut errors = error_inputs.iter().map(format_error_list).collect::>(); errors.sort(); @@ -651,7 +651,7 @@ impl TypingContext { let inputs = match node.construction_args { // If the node has a value input we can infer the return type from it ConstructionArgs::Value(ref v) => { - assert!(matches!(node.input, ProtoNodeInput::None)); + assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context))); // TODO: This should return a reference to the value let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![v.ty()]); self.inferred.insert(node_id, types.clone()); diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 924c907aa4..30247251ae 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -2,7 +2,8 @@ use dyn_any::DynAny; use graphene_core::raster::bbox::Bbox; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::{ - Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample, + Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, + Sample, }; use graphene_core::transform::Transform; use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node}; @@ -108,15 +109,7 @@ where image } -#[derive(Debug, Clone, Copy)] -pub struct InsertChannelNode { - insertion: Insertion, - target_channel: TargetChannel, - _p: PhantomData

, - _s: PhantomData, -} - -#[node_macro::old_node_fn(InsertChannelNode<_P, _S>)] +#[node_macro::node] fn insert_channel< // _P is the color of the input image. _P: RGBMut, @@ -125,8 +118,9 @@ fn insert_channel< Input: BitmapMut, Insertion: Bitmap, >( - mut image: Input, - insertion: Insertion, + _: impl Ctx, + #[implementations(ImageFrameTable)] mut image: Input, + #[implementations(ImageFrameTable)] insertion: Insertion, target_channel: RedGreenBlue, ) -> Input where @@ -155,15 +149,60 @@ where image } +#[node_macro::node] +fn combine_channels< + // _P is the color of the input image. + _P: RGBMut + AlphaMut, + _S: Pixel + Luminance, + // Input image + Input: BitmapMut, + Red: Bitmap, + Green: Bitmap, + Blue: Bitmap, + Alpha: Bitmap, +>( + _: impl Ctx, + #[implementations(ImageFrameTable)] mut image: Input, + #[implementations(ImageFrameTable)] red: Red, + #[implementations(ImageFrameTable)] green: Green, + #[implementations(ImageFrameTable)] blue: Blue, + #[implementations(ImageFrameTable)] alpha: Alpha, +) -> Input +where + _P::ColorChannel: Linear, +{ + let dimensions = [red.dim(), green.dim(), blue.dim(), alpha.dim()]; + if dimensions.iter().all(|&(x, _)| x == 0) { + return image; + } -#[derive(Debug, Clone, Copy)] -pub struct MaskImageNode { - stencil: Stencil, - _p: PhantomData

, - _s: PhantomData, + if dimensions.iter().any(|&(x, y)| x != image.width() || y != image.height()) { + log::warn!("Stencil and image have different sizes. This is not supported."); + return image; + } + + for y in 0..image.height() { + for x in 0..image.width() { + let image_pixel = image.get_pixel_mut(x, y).unwrap(); + if let Some(r) = red.get_pixel(x, y) { + image_pixel.set_red(r.l().cast_linear_channel()); + } + if let Some(g) = green.get_pixel(x, y) { + image_pixel.set_green(g.l().cast_linear_channel()); + } + if let Some(b) = blue.get_pixel(x, y) { + image_pixel.set_blue(b.l().cast_linear_channel()); + } + if let Some(a) = alpha.get_pixel(x, y) { + image_pixel.set_alpha(a.l().cast_linear_channel()); + } + } + } + + image } -#[node_macro::old_node_fn(MaskImageNode<_P, _S>)] +#[node_macro::node()] fn mask_image< // _P is the color of the input image. It must have an alpha channel because that is going to // be modified by the mask @@ -176,8 +215,9 @@ fn mask_image< // Stencil Stencil: Transform + Sample, >( - mut image: Input, - stencil: Stencil, + _: impl Ctx, + #[implementations(ImageFrameTable)] mut image: Input, + #[implementations(ImageFrameTable)] stencil: Stencil, ) -> Input { let image_size = DVec2::new(image.width() as f64, image.height() as f64); let mask_size = stencil.transform().decompose_scale(); diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 227b297652..0a8aa75497 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -226,7 +226,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( Context -> String, )] data: impl Node, Output = T>, - _surface_handle: impl Node<(), Output = Option>, + _surface_handle: impl Node, Output = Option>, ) -> RenderOutput { let footprint = render_config.viewport; let ctx = OwnedContextImpl::default().with_footprint(footprint).into_context(); @@ -237,7 +237,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( let data = data.eval(ctx.clone()).await; let editor_api = editor_api.eval(ctx).await; #[cfg(all(feature = "vello", target_arch = "wasm32"))] - let surface_handle = _surface_handle.eval(()).await; + let surface_handle = _surface_handle.eval(None).await; let use_vello = editor_api.editor_preferences.use_vello(); #[cfg(all(feature = "vello", target_arch = "wasm32"))] let use_vello = use_vello && surface_handle.is_some(); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index d50fe07db6..f8ad84c8e5 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -4,10 +4,8 @@ use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_core::fn_type; use graphene_core::ops::IdentityNode; use graphene_core::raster::color::Color; -use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; +use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::*; -use graphene_core::structural::Then; -use graphene_core::transform::Footprint; use graphene_core::value::{ClonedNode, ValueNode}; use graphene_core::vector::VectorDataTable; use graphene_core::{concrete, generic, Artboard, GraphicGroupTable}; @@ -15,14 +13,14 @@ use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::application_io::TextureFrame; -use graphene_std::raster::*; use graphene_std::wasm_application_io::*; +use graphene_std::Context; use graphene_std::{GraphicElement, GraphicGroup}; #[cfg(feature = "gpu")] use wgpu_executor::{ShaderInputFrame, WgpuExecutor}; use wgpu_executor::{WgpuSurface, WindowHandle}; -use glam::{DAffine2, DVec2, UVec2}; +use glam::{DVec2, UVec2}; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Arc; @@ -34,7 +32,7 @@ macro_rules! construct_node { let node = <$path>::new($( { let node = graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node")); - let value = node.eval(()).await; + let value = node.eval(None).await; graphene_core::value::ClonedNode::new(value) } ),* @@ -45,7 +43,7 @@ macro_rules! construct_node { macro_rules! register_node { ($path:ty, input: $input:ty, params: [ $($type:ty),*]) => { - register_node!($path, input: $input, fn_params: [ $(() => $type),*]) + register_node!($path, input: $input, fn_params: [ $(Context => $type),*]) }; ($path:ty, input: $input:ty, fn_params: [ $($arg:ty => $type:ty),*]) => { ( @@ -60,9 +58,9 @@ macro_rules! register_node { }, { let node = <$path>::new($( - graphene_std::any::PanicNode::<(), $type>::new() + graphene_std::any::PanicNode::::new() ),*); - let params = vec![$(fn_type!((), $type)),*]; + let params = vec![$(fn_type!(Context, $type)),*]; let mut node_io = <$path as NodeIO<'_, $input>>::to_node_io(&node, params); node_io.call_argument = concrete!(<$input as StaticType>::Static); node_io @@ -123,78 +121,14 @@ fn node_registry() -> HashMap, input: GraphicGroupTable, params: []), async_node!(graphene_core::ops::IntoNode, input: VectorDataTable, params: []), async_node!(graphene_core::ops::IntoNode, input: ImageFrameTable, params: []), - register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable, params: [ImageFrameTable]), - register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable, params: [ImageFrameTable, RedGreenBlue]), - // register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable, params: [ImageFrameTable]), - // register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable, params: [ImageFrameTable, RedGreenBlue]), - ( - ProtoNodeIdentifier::new("graphene_std::raster::CombineChannelsNode"), - |args| { - Box::pin(async move { - use graphene_core::raster::*; - use graphene_core::value::*; - - let channel_r: ImageFrameTable = DowncastBothNode::new(args[0].clone()).eval(()).await; - let channel_g: ImageFrameTable = DowncastBothNode::new(args[1].clone()).eval(()).await; - let channel_b: ImageFrameTable = DowncastBothNode::new(args[2].clone()).eval(()).await; - let channel_a: ImageFrameTable = DowncastBothNode::new(args[3].clone()).eval(()).await; - - let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red)); - let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green)); - let insert_b = InsertChannelNode::new(ClonedNode::new(channel_b.clone()), CopiedNode::new(RedGreenBlue::Blue)); - let complete_node = insert_r.then(insert_g).then(insert_b); - let complete_node = complete_node.then(MaskImageNode::new(ClonedNode::new(channel_a.clone()))); - - let channel_r = channel_r.one_item(); - let channel_g = channel_g.one_item(); - let channel_b = channel_b.one_item(); - let channel_a = channel_a.one_item(); - - // TODO: Move to FN Node for better performance - let (mut transform, mut bounds) = (DAffine2::ZERO, glam::UVec2::ZERO); - for image in [channel_a, channel_r, channel_g, channel_b] { - if image.image.width() > bounds.x { - bounds = glam::UVec2::new(image.image.width(), image.image.height()); - transform = image.transform; - } - } - let empty_image = ImageFrame { - image: Image::new(bounds.x, bounds.y, Color::BLACK), - transform, - ..Default::default() - }; - let empty_image = ImageFrameTable::new(empty_image); - let final_image = ClonedNode::new(empty_image).then(complete_node); - let final_image = FutureWrapperNode::new(final_image); - - let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(final_image); - any.into_type_erased() - }) - }, - NodeIOTypes::new( - concrete!(()), - concrete!(ImageFrameTable), - vec![ - fn_type!(ImageFrameTable), - fn_type!(ImageFrameTable), - fn_type!(ImageFrameTable), - fn_type!(ImageFrameTable), - ], - ), - ), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [ImageFrameTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [TextureFrame]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => VectorDataTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroupTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicGroupTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicElement]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => Artboard]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageFrameTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => TextureFrame]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]), #[cfg(feature = "gpu")] - register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: (), params: [&WasmEditorApi]), + register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: Context, params: [&WasmEditorApi]), #[cfg(feature = "gpu")] ( ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"), @@ -337,44 +271,29 @@ fn node_registry() -> HashMap, input: (), params: [Image]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [VectorDataTable]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrameTable]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Vec]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Arc]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [WindowHandle]), - #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ShaderInputFrame]), - #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WgpuSurface]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WindowHandle]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [graphene_std::SurfaceFrame]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [RenderOutput]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Image]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Vec]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Arc]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => WindowHandle]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ShaderInputFrame]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => wgpu_executor::WgpuSurface]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => wgpu_executor::WindowHandle]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => graphene_std::SurfaceFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuSurface]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WindowHandle]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => RenderOutput]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroup]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroup]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => WgpuSurface]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => Option]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]), - register_node!(graphene_core::structural::ConsNode<(), _>, input: Image, params: [&str]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ShaderInputFrame]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => TextureFrame]), ]; let mut map: HashMap> = HashMap::new(); for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() { diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index dbf080faa0..f419def798 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -7,7 +7,7 @@ use graph_craft::{ wasm_application_io::WasmEditorApi, ProtoNodeIdentifier, }; -use graphene_std::{transform::Footprint, uuid::NodeId}; +use graphene_std::{uuid::NodeId, Context}; // TODO: this is copy pasta from the editor (and does get out of sync) pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc) -> NodeNetwork { @@ -32,13 +32,13 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc Date: Wed, 29 Jan 2025 21:16:06 +0100 Subject: [PATCH 19/34] Improve debugging for footprint not found errors --- node-graph/gcore/src/context.rs | 41 +++++++++++++++++----- node-graph/gcore/src/transform.rs | 2 +- node-graph/gstd/src/wasm_application_io.rs | 4 +-- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 8cd5a85c15..f0eb97697d 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -1,4 +1,4 @@ -use core::{any::Any, borrow::Borrow}; +use core::{any::Any, borrow::Borrow, panic::Location}; use std::sync::Arc; use crate::transform::Footprint; @@ -6,10 +6,12 @@ use crate::transform::Footprint; pub trait Ctx: Clone + Send {} pub trait ExtractFootprint { + #[track_caller] fn try_footprint(&self) -> Option<&Footprint>; + #[track_caller] fn footprint(&self) -> &Footprint { self.try_footprint().unwrap_or_else(|| { - log::error!("Context did not have a footprint"); + log::error!("Context did not have a footprint, called from: {}", Location::caller()); &const { Footprint::empty() } }) } @@ -50,6 +52,7 @@ impl Ctx for () {} impl Ctx for Footprint {} impl ExtractFootprint for () { fn try_footprint(&self) -> Option<&Footprint> { + log::error!("tried to extract footprint form (), {}", Location::caller()); None } } @@ -62,6 +65,9 @@ impl ExtractFootprint for &T { impl ExtractFootprint for Option { fn try_footprint(&self) -> Option<&Footprint> { + if self.is_none() { + log::warn!("trying to extract footprint from context None {} ", Location::caller()); + } self.as_ref().and_then(|x| x.try_footprint()) } } @@ -208,14 +214,22 @@ pub type Context<'a> = Option>; type DynRef<'a> = &'a (dyn Any + Send + Sync); type DynBox = Box; -#[derive(Default, dyn_any::DynAny)] +#[derive(dyn_any::DynAny)] pub struct OwnedContextImpl { - pub footprint: Option, - pub varargs: Option>, - pub parent: Option>, + footprint: Option, + varargs: Option>, + parent: Option>, // This could be converted into a single enum to save extra bytes - pub index: Option, - pub time: Option, + index: Option, + time: Option, +} + +impl Default for OwnedContextImpl { + #[track_caller] + fn default() -> Self { + log::debug!("creating new context from {}", Location::caller()); + Self::empty() + } } impl core::hash::Hash for OwnedContextImpl { @@ -229,7 +243,9 @@ impl core::hash::Hash for OwnedContextImpl { } impl OwnedContextImpl { + #[track_caller] pub fn from(value: T) -> Self { + log::debug!("converting context called from: {}", Location::caller()); let footprint = value.try_footprint().copied(); let index = value.try_index(); let time = value.try_time(); @@ -242,6 +258,15 @@ impl OwnedContextImpl { time, } } + pub const fn empty() -> Self { + OwnedContextImpl { + footprint: None, + varargs: None, + parent: None, + index: None, + time: None, + } + } } impl OwnedContextImpl { diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 5a7bd19b58..c396d74113 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -325,7 +325,7 @@ async fn transform( if !footprint.ignore_modifications { footprint.apply_transform(&modification); } - let mut ctx = OwnedContextImpl::from(ctx).with_footprint(footprint); + let ctx = OwnedContextImpl::from(ctx).with_footprint(footprint); let mut transform_target = transform_target.eval(ctx.into_context()).await; diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 0a8aa75497..3b0e1b4f4d 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -235,9 +235,9 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); let data = data.eval(ctx.clone()).await; - let editor_api = editor_api.eval(ctx).await; + let editor_api = editor_api.eval(ctx.clone()).await; #[cfg(all(feature = "vello", target_arch = "wasm32"))] - let surface_handle = _surface_handle.eval(None).await; + let surface_handle = _surface_handle.eval(ctx.clone()).await; let use_vello = editor_api.editor_preferences.use_vello(); #[cfg(all(feature = "vello", target_arch = "wasm32"))] let use_vello = use_vello && surface_handle.is_some(); From 0212f5a9ef159119841984333035064c6932edd7 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 29 Jan 2025 23:50:25 +0100 Subject: [PATCH 20/34] Forward context in append artboard node --- node-graph/gcore/src/context.rs | 16 +++++++++------- node-graph/gcore/src/graphic_element.rs | 20 ++++++++++++++++---- node-graph/gcore/src/transform.rs | 11 +++++++---- node-graph/gstd/src/wasm_application_io.rs | 3 ++- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index f0eb97697d..cf86e9f6c4 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -65,11 +65,15 @@ impl ExtractFootprint for &T { impl ExtractFootprint for Option { fn try_footprint(&self) -> Option<&Footprint> { - if self.is_none() { - log::warn!("trying to extract footprint from context None {} ", Location::caller()); - } self.as_ref().and_then(|x| x.try_footprint()) } + #[track_caller] + fn footprint(&self) -> &Footprint { + self.try_footprint().unwrap_or_else(|| { + log::warn!("trying to extract footprint from context None {} ", Location::caller()); + &const { Footprint::empty() } + }) + } } impl ExtractTime for Option { fn try_time(&self) -> Option { @@ -204,9 +208,9 @@ impl ExtractVarArgs for OwnedContextImpl { } } -impl CloneVarArgs for OwnedContextImpl { +impl CloneVarArgs for Arc { fn arc_clone(&self) -> Option> { - todo!() + Some(self.clone()) } } @@ -227,7 +231,6 @@ pub struct OwnedContextImpl { impl Default for OwnedContextImpl { #[track_caller] fn default() -> Self { - log::debug!("creating new context from {}", Location::caller()); Self::empty() } } @@ -245,7 +248,6 @@ impl core::hash::Hash for OwnedContextImpl { impl OwnedContextImpl { #[track_caller] pub fn from(value: T) -> Self { - log::debug!("converting context called from: {}", Location::caller()); let footprint = value.try_footprint().copied(); let index = value.try_index(); let time = value.try_time(); diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 6f8804fecd..1b0b87621c 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -379,9 +379,12 @@ async fn to_artboard + 'n>( background: Color, clip: bool, ) -> Artboard { - let mut footprint = *ctx.footprint(); - footprint.translate(location.as_dvec2()); - let new_ctx = OwnedContextImpl::from(ctx).with_footprint(footprint); + let footprint = ctx.try_footprint().copied(); + let mut new_ctx = OwnedContextImpl::from(ctx); + if let Some(mut footprint) = footprint { + footprint.translate(location.as_dvec2()); + new_ctx = new_ctx.with_footprint(footprint); + } let graphic_group = contents.eval(new_ctx.into_context()).await; Artboard { @@ -395,7 +398,16 @@ async fn to_artboard + 'n>( } #[node_macro::node(category(""))] -async fn append_artboard(_: impl Ctx, mut artboards: ArtboardGroup, artboard: Artboard, node_path: Vec) -> ArtboardGroup { +async fn append_artboard( + #[implementations(Context)] ctx: C, + #[implementations(Context -> ArtboardGroup)] artboards: impl Node, + #[implementations(Context -> Artboard)] artboard: impl Node, + node_path: Vec, +) -> ArtboardGroup { + let mut artboards = artboards.eval(ctx.clone()).await; + let artboard = artboard.eval(ctx).await; + // let foot = ctx.footprint(); + // log::debug!("{:?}", foot); // Get the penultimate element of the node path, or None if the path is too short let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); artboards.append_artboard(artboard, encapsulating_node_id); diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index c396d74113..31ea15387c 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -320,12 +320,15 @@ async fn transform( _pivot: DVec2, ) -> T { let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]); - let mut footprint = *ctx.footprint(); + let footprint = ctx.try_footprint().copied(); - if !footprint.ignore_modifications { - footprint.apply_transform(&modification); + let mut ctx = OwnedContextImpl::from(ctx); + if let Some(mut footprint) = footprint { + if !footprint.ignore_modifications { + footprint.apply_transform(&modification); + } + ctx = ctx.with_footprint(footprint); } - let ctx = OwnedContextImpl::from(ctx).with_footprint(footprint); let mut transform_target = transform_target.eval(ctx.into_context()).await; diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 3b0e1b4f4d..f237da458b 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -12,7 +12,7 @@ use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::transform::Footprint; use graphene_core::vector::VectorDataTable; -use graphene_core::{Color, Context, Ctx, GraphicGroupTable, OwnedContextImpl, WasmNotSend}; +use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend}; #[cfg(target_arch = "wasm32")] use base64::Engine; @@ -230,6 +230,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( ) -> RenderOutput { let footprint = render_config.viewport; let ctx = OwnedContextImpl::default().with_footprint(footprint).into_context(); + ctx.footprint(); let RenderConfig { hide_artboards, for_export, .. } = render_config; let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); From 8da6c0e563176e141cc3b495cc71a0337b4724b6 Mon Sep 17 00:00:00 2001 From: hypercube <0hypercube@gmail.com> Date: Sun, 2 Feb 2025 15:04:02 +0000 Subject: [PATCH 21/34] Fix thumbnails --- editor/src/node_graph_executor.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 28ab819b8e..00269dd477 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -15,6 +15,7 @@ use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment}; use graphene_core::text::FontCache; use graphene_core::transform::Footprint; use graphene_core::vector::style::ViewMode; +use graphene_core::Context; use graphene_std::renderer::{format_transform_matrix, RenderMetadata}; use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; @@ -286,17 +287,17 @@ impl NodeRuntime { continue; }; - if let Some(io) = introspected_data.downcast_ref::>() { + if let Some(io) = introspected_data.downcast_ref::>() { Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) } else if let Some(io) = introspected_data.downcast_ref::>() { Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) - } else if let Some(io) = introspected_data.downcast_ref::>() { + } else if let Some(io) = introspected_data.downcast_ref::>() { Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) } else if let Some(io) = introspected_data.downcast_ref::>() { Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) } // Insert the vector modify if we are dealing with vector data - else if let Some(record) = introspected_data.downcast_ref::>() { + else if let Some(record) = introspected_data.downcast_ref::>() { self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone()); } else if let Some(record) = introspected_data.downcast_ref::>() { self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone()); From 2aad15ab8ea3b064ff9a33317eeec46f47b79e88 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 3 Feb 2025 03:19:47 -0800 Subject: [PATCH 22/34] Fix loading most demo artwork --- node-graph/gcore/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 1868a4f7a4..39426f0ca8 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -134,6 +134,7 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: let name = String::deserialize(deserializer)?; let name = match name.as_str() { "f32" => "f64".to_string(), + "graphene_core::transform::Footprint" => "core::option::Option>".to_string(), "graphene_core::graphic_element::GraphicGroup" => "graphene_core::graphic_element::Instances".to_string(), "graphene_core::vector::vector_data::VectorData" => "graphene_core::graphic_element::Instances".to_string(), "graphene_core::raster::image::ImageFrame" => "graphene_core::graphic_element::Instances>".to_string(), From de528856f1053b431361ddf04618f09ac5359d78 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 30 Jan 2025 17:13:16 +0100 Subject: [PATCH 23/34] Wrap output type of all nodes in future --- node-graph/gcore/src/generic.rs | 1 + node-graph/gcore/src/logic.rs | 2 +- node-graph/gcore/src/ops.rs | 2 +- node-graph/gstd/src/brush.rs | 80 +++++++++++++------ node-graph/gstd/src/raster.rs | 22 ++--- .../interpreted-executor/src/node_registry.rs | 75 ++++++++++------- node-graph/node-macro/src/codegen.rs | 32 +++++--- 7 files changed, 135 insertions(+), 79 deletions(-) diff --git a/node-graph/gcore/src/generic.rs b/node-graph/gcore/src/generic.rs index 49f3123f95..bd82d3f080 100644 --- a/node-graph/gcore/src/generic.rs +++ b/node-graph/gcore/src/generic.rs @@ -1,6 +1,7 @@ use core::marker::PhantomData; use crate::Node; +#[derive(Clone)] pub struct FnNode O, I, O>(T, PhantomData<(I, O)>); impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode { diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 494a473596..0e4214f060 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -11,7 +11,7 @@ fn log_to_console(_: impl Ctx, #[implementations(String, bo value } -#[node_macro::node(category("Debug"))] +#[node_macro::node(category("Debug"), skip_impl)] fn to_string(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String { format!("{:?}", value) } diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index bf91ae5a65..5da6e1f08e 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -485,7 +485,7 @@ fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 { // TODO: Rename to "Passthrough" /// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. #[node_macro::node(skip_impl)] -fn identity<'i, T: 'i>(value: T) -> T { +fn identity<'i, T: 'i + Send>(value: T) -> T { value } diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index e1d88a812f..933aa14336 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -1,5 +1,7 @@ use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, ExtendImageToBoundsNode}; +use graph_craft::generic::FnNode; +use graph_craft::proto::FutureWrapperNode; use graphene_core::raster::adjustments::blend_colors; use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::brush_cache::BrushCache; @@ -131,14 +133,21 @@ where target } -pub fn create_brush_texture(brush_style: &BrushStyle) -> Image { +pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image { let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); - let blank_texture = EmptyImageNode::new(CopiedNode::new(transform), CopiedNode::new(Color::TRANSPARENT)).eval(()); - let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.))); + use crate::raster::empty_image; + let blank_texture = empty_image((), transform, Color::TRANSPARENT); + let normal_blend = BlendColorPairNode::new( + FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))), + FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))), + ); normal_blend.eval((Color::default(), Color::default())); - let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend)); - blend_executor.eval((blank_texture, stamp)).image + // use crate::raster::blend_image_tuple; + // blend_image_tuple((blank_texture, stamp), &normal_blend).await.image; + crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 100.)).image + // let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend))); + // blend_executor.eval((blank_texture, stamp)).image } macro_rules! inline_blend_funcs { @@ -202,7 +211,7 @@ pub fn blend_with_mode(background: ImageFrame, foreground: ImageFrame, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { +async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { let image = image.one_item().clone(); let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); @@ -225,11 +234,13 @@ fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTable, bounds: ImageFrameTable, bounds: ImageFrameTable = stroke.compute_blit_points().into_iter().collect(); match stroke.style.blend_mode { BlendMode::Erase => { - let blend_params = BlendColorPairNode::new(ValueNode(CopiedNode::new(BlendMode::Erase)), ValueNode(CopiedNode::new(100.))); - let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params)); - erase_restore_mask = blit_node.eval(erase_restore_mask); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 100.)); + let blit_node = BlitNode::new( + FutureWrapperNode::new(ClonedNode::new(brush_texture)), + FutureWrapperNode::new(ClonedNode::new(positions)), + FutureWrapperNode::new(ClonedNode::new(blend_params)), + ); + erase_restore_mask = blit_node.eval(erase_restore_mask).await; } // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. BlendMode::Restore => { - let blend_params = BlendColorPairNode::new(ValueNode(CopiedNode::new(BlendMode::Restore)), ValueNode(CopiedNode::new(100.))); - let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params)); - erase_restore_mask = blit_node.eval(erase_restore_mask); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 100.)); + let blit_node = BlitNode::new( + FutureWrapperNode::new(ClonedNode::new(brush_texture)), + FutureWrapperNode::new(ClonedNode::new(positions)), + FutureWrapperNode::new(ClonedNode::new(blend_params)), + ); + erase_restore_mask = blit_node.eval(erase_restore_mask).await; } _ => unreachable!(), } } - let blend_params = BlendColorPairNode::new(ValueNode(CopiedNode::new(BlendMode::MultiplyAlpha)), ValueNode(CopiedNode::new(100.))); - let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params)); - actual_image = blend_executor.eval((actual_image, erase_restore_mask)); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 100.)); + let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params))); + actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await; } ImageFrameTable::new(actual_image) diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 27d393b938..c836703f2b 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -29,7 +29,7 @@ impl From for Error { } #[node_macro::node(category("Debug: Raster"))] -fn sample_image(ctx: impl ExtractFootprint + Clone, image_frame: ImageFrameTable) -> ImageFrameTable { +fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable) -> ImageFrameTable { let image_frame = image_frame.one_item(); // Resize the image using the image crate @@ -248,17 +248,17 @@ fn mask_image< image } -#[derive(Debug, Clone, Copy)] -pub struct BlendImageTupleNode { - map_fn: MapFn, - _p: PhantomData

, - _fg: PhantomData, -} +// #[derive(Debug, Clone, Copy)] +// pub struct BlendImageTupleNode { +// map_fn: MapFn, +// _p: PhantomData

, +// _fg: PhantomData, +// } -#[node_macro::old_node_fn(BlendImageTupleNode<_P, _Fg>)] -fn blend_image_tuple<_P: Alpha + Pixel + Debug, MapFn, _Fg: Sample + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'input MapFn) -> ImageFrame<_P> +#[node_macro::node(skip_impl)] +async fn blend_image_tuple<_P: Alpha + Pixel + Debug + Send, MapFn, _Fg: Sample + Transform + Clone + Send + 'n>(images: (ImageFrame<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrame<_P> where - MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone, + MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone, { let (background, foreground) = images; @@ -628,7 +628,7 @@ fn noise_pattern( } #[node_macro::node(category("Raster: Generator"))] -fn mandelbrot(ctx: impl ExtractFootprint) -> ImageFrameTable { +fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable { let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index f8ad84c8e5..8fa25e3a9b 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,4 +1,4 @@ -use dyn_any::StaticType; +use dyn_any::{DynFuture, StaticType}; use graph_craft::document::value::RenderOutput; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_core::fn_type; @@ -32,6 +32,7 @@ macro_rules! construct_node { let node = <$path>::new($( { let node = graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node")); + // let node = graphene_std::any::FutureWrapperNode::new(node); let value = node.eval(None).await; graphene_core::value::ClonedNode::new(value) } @@ -51,7 +52,6 @@ macro_rules! register_node { |args| { Box::pin(async move { let node = construct_node!(args, $path, [$($arg => $type),*]).await; - let node = graphene_std::any::FutureWrapperNode::new(node); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node); Box::new(any) as TypeErasedBox }) @@ -105,11 +105,11 @@ macro_rules! async_node { // TODO: turn into hashmap fn node_registry() -> HashMap> { let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ - ( - ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), - |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }), - NodeIOTypes::new(generic!(I), generic!(I), vec![]), - ), + // ( + // ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), + // |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }), + // NodeIOTypes::new(generic!(I), generic!(I), vec![]), + // ), // async_node!(graphene_core::ops::IntoNode>, input: ImageFrameTable, params: []), // async_node!(graphene_core::ops::IntoNode>, input: ImageFrameTable, params: []), async_node!(graphene_core::ops::IntoNode, input: ImageFrameTable, params: []), @@ -128,7 +128,26 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => GraphicElement]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]), #[cfg(feature = "gpu")] - register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: Context, params: [&WasmEditorApi]), + ( + ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)), + |args| { + Box::pin(async move { + let editor_api: DowncastBothNode = DowncastBothNode::new(args[1].clone()); + let node = >::new(editor_api); + // let node = FutureWrapperNode::new(node); + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); + Box::new(any) as TypeErasedBox + }) + }, + { + let node = >::new(graphene_std::any::PanicNode::>::new()); + let params = vec![fn_type!(Context, DynFuture<'static, &WasmEditorApi>)]; + let mut node_io = as NodeIO<'_, Context>>::to_async_node_io(&node, params); + node_io.call_argument = concrete!(::Static); + node_io + }, + ), + // register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: Context, params: [&WasmEditorApi]), #[cfg(feature = "gpu")] ( ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"), @@ -213,27 +232,27 @@ fn node_registry() -> HashMap), concrete!(ImageFrameTable), vec![fn_type!(graphene_core::raster::curve::Curve)]), // ), // TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color. - ( - ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), - |args| { - use graphene_core::raster::{curve::Curve, GenerateCurvesNode}; - let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone()); - Box::pin(async move { - let curve = ClonedNode::new(curve.eval(()).await); + // ( + // ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), + // |args| { + // use graphene_core::raster::{curve::Curve, GenerateCurvesNode}; + // let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone()); + // Box::pin(async move { + // let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await)); - let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32)); - let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(()))); - let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); - let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); - any.into_type_erased() - }) - }, - NodeIOTypes::new( - concrete!(ImageFrameTable), - concrete!(ImageFrameTable), - vec![fn_type!(graphene_core::raster::curve::Curve)], - ), - ), + // let generate_curves_node = GenerateCurvesNode::new(FutureWrapperNode::new(curve), FutureWrapperNode::new(ClonedNode::new(0_f32))); + // let map_image_frame_node = graphene_std::raster::MapImageNode::new(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(())))); + // let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); + // let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); + // any.into_type_erased() + // }) + // }, + // NodeIOTypes::new( + // concrete!(ImageFrameTable), + // concrete!(ImageFrameTable), + // vec![fn_type!(graphene_core::raster::curve::Curve)], + // ), + // ), // ( // ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"), // |args: Vec| { diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index fa1a56cf7a..ad6abcbbc6 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -154,7 +154,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let name = &pat_ident.ident; - quote! { let #name = self.#name.eval(__input.clone()); } + quote! { let #name = self.#name.eval(__input.clone()).await; } // quote! { let #name = self.#name.eval(()); } } ParsedField::Node { pat_ident, .. } => { @@ -178,13 +178,18 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all"); + let id = future_idents.len(); + let fut_ident = format_ident!("F{}", id); + future_idents.push(fut_ident.clone()); quote!( + #fut_ident: core::future::Future + #graphene_core::WasmNotSend + 'n, for<'all> #all_lifetime_ty: #graphene_core::WasmNotSend, // #name: 'n, - #name: #graphene_core::Node<'n, #input_type, Output = #ty> + #name: #graphene_core::Node<'n, #input_type, Output = #fut_ident> + #graphene_core::WasmNotSync ) } (ParsedField::Node { input_type, output_type, .. }, false) => { + unreachable!(); quote!( // #name: 'n, #name: #graphene_core::Node<'n, #input_type, Output = #output_type> + #graphene_core::WasmNotSync @@ -220,14 +225,17 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result; #[inline] fn eval(&'n self, __input: #input_type) -> Self::Output { - #(#eval_args)* - Box::pin(self::#fn_name(__input #(, #field_names)*)) + Box::pin(async move { + #(#eval_args)* + self::#fn_name(__input #(, #field_names)*) #await_keyword + }) } } } else { @@ -255,7 +263,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body + pub(crate) #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body #[automatically_derived] impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> @@ -365,7 +373,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st .collect(); let max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max(); - let future_node = (!parsed.is_async).then(|| quote!(let node = gcore::registry::FutureWrapperNode::new(node);)); + let future_node = (!parsed.is_async && false).then(|| quote!(let node = gcore::registry::FutureWrapperNode::new(node);)); for i in 0..max_implementations.unwrap_or(0) { let mut temp_constructors = Vec::new(); @@ -389,14 +397,14 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st } else { quote!( #downcast_node - let #field_name = #field_name.eval(None).await; - let #field_name = ClonedNode::new(#field_name); - let #field_name: TypeNode<_, #input_type, #output_type> = TypeNode::new(#field_name); + // let #field_name = #field_name.eval(None).await; + // let #field_name = ClonedNode::new(#field_name); + // let #field_name: TypeNode<_, #input_type, #output_type> = TypeNode::new(#field_name); // try polling futures ) }); temp_node_io.push(quote!(fn_type!(#input_type, #output_type, alias: #output_type))); - match parsed.is_async && *impl_node { + match (parsed.is_async || true) && *impl_node || true { true => panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)), false => panic_node_types.push(quote!(#input_type, #output_type)), }; @@ -405,7 +413,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st true => parsed.input.ty.clone(), false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(), }; - let node_io = if parsed.is_async { quote!(to_async_node_io) } else { quote!(to_node_io) }; + let node_io = if (parsed.is_async || true) { quote!(to_async_node_io) } else { quote!(to_node_io) }; constructors.push(quote!( ( |args| { From 459cbf77daddcfac60f25fdd1213895fa56d4075 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 4 Feb 2025 13:50:01 +0100 Subject: [PATCH 24/34] Encode futures as part of the type --- node-graph/gcore/src/graphic_element.rs | 11 +--- node-graph/gcore/src/lib.rs | 3 +- node-graph/gcore/src/types.rs | 15 +++++ node-graph/graph-craft/src/proto.rs | 4 +- .../interpreted-executor/src/node_registry.rs | 55 ++----------------- node-graph/node-macro/src/codegen.rs | 4 +- 6 files changed, 29 insertions(+), 63 deletions(-) diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 1b0b87621c..90997d10e9 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -398,14 +398,9 @@ async fn to_artboard + 'n>( } #[node_macro::node(category(""))] -async fn append_artboard( - #[implementations(Context)] ctx: C, - #[implementations(Context -> ArtboardGroup)] artboards: impl Node, - #[implementations(Context -> Artboard)] artboard: impl Node, - node_path: Vec, -) -> ArtboardGroup { - let mut artboards = artboards.eval(ctx.clone()).await; - let artboard = artboard.eval(ctx).await; +async fn append_artboard(ctx: impl Ctx, mut artboards: ArtboardGroup, artboard: Artboard, node_path: Vec) -> ArtboardGroup { + // let mut artboards = artboards.eval(ctx.clone()).await; + // let artboard = artboard.eval(ctx).await; // let foot = ctx.footprint(); // log::debug!("{:?}", foot); // Get the penultimate element of the node path, or None if the path is too short diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 0758583054..e419bc4cf5 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -114,8 +114,7 @@ where { NodeIOTypes { call_argument: concrete!(::Static), - // TODO return actual future type - return_value: concrete!(<::Output as StaticTypeSized>::Static), + return_value: future!(<::Output as StaticTypeSized>::Static), inputs, } } diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 39426f0ca8..f5005e9c84 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -53,6 +53,9 @@ macro_rules! future { ($type:ty) => {{ $crate::Type::Future(Box::new(concrete!($type))) }}; + ($type:ty, $name:ty) => { + $crate::Type::Future(Box::new(concrete!($type, $name))) + }; } #[macro_export] @@ -67,6 +70,18 @@ macro_rules! fn_type { $crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type))) }; } +#[macro_export] +macro_rules! fn_type_fut { + ($type:ty) => { + $crate::Type::Fn(Box::new(concrete!(())), Box::new(future!($type))) + }; + ($in_type:ty, $type:ty, alias: $outname:ty) => { + $crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type, $outname))) + }; + ($in_type:ty, $type:ty) => { + $crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type))) + }; +} #[derive(Clone, PartialEq, Eq, Hash, Default)] pub struct NodeIOTypes { diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index f9f162b6a7..d685cbb74d 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -653,7 +653,7 @@ impl TypingContext { ConstructionArgs::Value(ref v) => { assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context))); // TODO: This should return a reference to the value - let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![v.ty()]); + let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![v.ty()]); self.inferred.insert(node_id, types.clone()); return Ok(types); } @@ -696,6 +696,8 @@ impl TypingContext { match (from, to) { // Direct comparison of two concrete types. (Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2, + // Check inner type for futures + (Type::Future(type1), Type::Future(type2)) => type1 == type2, // Loose comparison of function types, where loose means that functions are considered on a "greater than or equal to" basis of its function type's generality. // That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature. // For example, we allow `T -> V` to be substituted with `T' -> V` or `() -> V` where T' and () are more specific than T. diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 8fa25e3a9b..a38870deeb 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -9,6 +9,7 @@ use graphene_core::raster::*; use graphene_core::value::{ClonedNode, ValueNode}; use graphene_core::vector::VectorDataTable; use graphene_core::{concrete, generic, Artboard, GraphicGroupTable}; +use graphene_core::{fn_type_fut, future}; use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; @@ -25,49 +26,6 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Arc; -macro_rules! construct_node { - ($args: ident, $path:ty, [$($arg:ty => $type:ty),*]) => { async move { - let mut args = $args.clone(); - args.reverse(); - let node = <$path>::new($( - { - let node = graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node")); - // let node = graphene_std::any::FutureWrapperNode::new(node); - let value = node.eval(None).await; - graphene_core::value::ClonedNode::new(value) - } - ),* - ); - node - }} -} - -macro_rules! register_node { - ($path:ty, input: $input:ty, params: [ $($type:ty),*]) => { - register_node!($path, input: $input, fn_params: [ $(Context => $type),*]) - }; - ($path:ty, input: $input:ty, fn_params: [ $($arg:ty => $type:ty),*]) => { - ( - ProtoNodeIdentifier::new(stringify!($path)), - |args| { - Box::pin(async move { - let node = construct_node!(args, $path, [$($arg => $type),*]).await; - let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node); - Box::new(any) as TypeErasedBox - }) - }, - { - let node = <$path>::new($( - graphene_std::any::PanicNode::::new() - ),*); - let params = vec![$(fn_type!(Context, $type)),*]; - let mut node_io = <$path as NodeIO<'_, $input>>::to_node_io(&node, params); - node_io.call_argument = concrete!(<$input as StaticType>::Static); - node_io - }, - ) - }; -} macro_rules! async_node { // TODO: we currently need to annotate the type here because the compiler would otherwise (correctly) // TODO: assign a Pin>> type to the node, which is not what we want for now. @@ -93,7 +51,7 @@ macro_rules! async_node { ),*); // TODO: Propagate the future type through the node graph // let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*]; - let params = vec![$(fn_type!($arg, $type)),*]; + let params = vec![$(fn_type_fut!($arg, $type)),*]; let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params); node_io.call_argument = concrete!(<$input as StaticType>::Static); node_io @@ -132,22 +90,20 @@ fn node_registry() -> HashMap)), |args| { Box::pin(async move { - let editor_api: DowncastBothNode = DowncastBothNode::new(args[1].clone()); + let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[0].clone()); let node = >::new(editor_api); - // let node = FutureWrapperNode::new(node); - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); + let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(node); Box::new(any) as TypeErasedBox }) }, { let node = >::new(graphene_std::any::PanicNode::>::new()); - let params = vec![fn_type!(Context, DynFuture<'static, &WasmEditorApi>)]; + let params = vec![fn_type_fut!(Context, &WasmEditorApi)]; let mut node_io = as NodeIO<'_, Context>>::to_async_node_io(&node, params); node_io.call_argument = concrete!(::Static); node_io }, ), - // register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: Context, params: [&WasmEditorApi]), #[cfg(feature = "gpu")] ( ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"), @@ -155,7 +111,6 @@ fn node_registry() -> HashMap = DowncastBothNode::new(args[0].clone()); let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone()); - // let document_node = ClonedNode::new(document_node.eval(())); let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(node); any.into_type_erased() diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index ad6abcbbc6..745850d1d6 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -278,7 +278,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)), false => panic_node_types.push(quote!(#input_type, #output_type)), From 19db70997cd3341288efb2a7c2e466c4124dbe8c Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 12 Feb 2025 17:35:12 +0100 Subject: [PATCH 25/34] Fix document node definitions for future types --- .../document/node_graph/document_node_definitions.rs | 2 +- node-graph/gcore/src/types.rs | 4 ++-- node-graph/graph-craft/src/document/value.rs | 6 +++--- node-graph/graph-craft/src/proto.rs | 4 +++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 465e50988d..c121492d15 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -2721,7 +2721,7 @@ fn static_nodes() -> Vec { .zip(first_node_io.inputs.iter()) .enumerate() .map(|(index, (field, ty))| { - let exposed = if index == 0 { *ty != fn_type!(()) } else { field.exposed }; + let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; match field.value_source { RegistryValueSource::None => {} diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index f5005e9c84..86a034cf71 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -205,7 +205,7 @@ pub enum Type { /// Runtime type information for a function. Given some input, gives some output. /// See the example and explanation in the `ComposeNode` implementation within the node registry for more info. Fn(Box, Box), - /// Not used at the moment. + /// Represents a future which promises to return the inner type. Future(Box), } @@ -296,7 +296,7 @@ impl Type { Self::Generic(_) => self, Self::Concrete(_) => self, Self::Fn(_, output) => output.nested_type(), - Self::Future(_) => self, + Self::Future(output) => output.nested_type(), } } } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 81034f915d..d2b83c83de 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -102,8 +102,8 @@ macro_rules! tagged_value { }) } Type::Fn(_, output) => TaggedValue::from_type(output), - Type::Future(_) => { - None + Type::Future(output) => { + TaggedValue::from_type(output) } } } @@ -270,7 +270,7 @@ impl TaggedValue { Some(ty) } Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output), - Type::Future(_) => None, + Type::Future(fut) => TaggedValue::from_primitive_string(string, fut), } } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index d685cbb74d..143b7c4c86 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -203,6 +203,7 @@ impl ProtoNode { if self.skip_deduplication { self.original_location.path.hash(&mut hasher); } + std::mem::discriminant(&self.input).hash(&mut hasher); match self.input { ProtoNodeInput::None => (), @@ -212,6 +213,7 @@ impl ProtoNode { ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher), ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher), }; + Some(NodeId(hasher.finish())) } @@ -653,7 +655,7 @@ impl TypingContext { ConstructionArgs::Value(ref v) => { assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context))); // TODO: This should return a reference to the value - let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![v.ty()]); + let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]); self.inferred.insert(node_id, types.clone()); return Ok(types); } From 798c5e115ec319cf4ff3ce133621c9c0c8f584a2 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 13 Feb 2025 15:56:22 -0800 Subject: [PATCH 26/34] Remove Clippy warnings --- node-graph/gcore/src/graphic_element.rs | 4 ++++ node-graph/gstd/src/brush.rs | 16 ++++++++-------- .../interpreted-executor/src/node_registry.rs | 1 - node-graph/node-macro/src/codegen.rs | 12 ++++++------ 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 90997d10e9..4ffd545480 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -404,6 +404,10 @@ async fn append_artboard(ctx: impl Ctx, mut artboards: ArtboardGroup, artboard: // let foot = ctx.footprint(); // log::debug!("{:?}", foot); // Get the penultimate element of the node path, or None if the path is too short + + // TODO: Delete this line + let _ctx = ctx; + let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); artboards.append_artboard(artboard, encapsulating_node_id); diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 933aa14336..25665c8cc1 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -1,4 +1,4 @@ -use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, ExtendImageToBoundsNode}; +use crate::raster::{blend_image_closure, BlendImageTupleNode, ExtendImageToBoundsNode}; use graph_craft::generic::FnNode; use graph_craft::proto::FutureWrapperNode; @@ -7,7 +7,7 @@ use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::BlendMode; -use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, Pixel, Sample}; +use graphene_core::raster::{Alpha, Color, Image, Pixel, Sample}; use graphene_core::transform::{Transform, TransformMut}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; @@ -138,11 +138,11 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image { let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); use crate::raster::empty_image; let blank_texture = empty_image((), transform, Color::TRANSPARENT); - let normal_blend = BlendColorPairNode::new( - FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))), - FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))), - ); - normal_blend.eval((Color::default(), Color::default())); + // let normal_blend = BlendColorPairNode::new( + // FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))), + // FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))), + // ); + // normal_blend.eval((Color::default(), Color::default())); // use crate::raster::blend_image_tuple; // blend_image_tuple((blank_texture, stamp), &normal_blend).await.image; crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 100.)).image @@ -257,7 +257,7 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.); let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size); - let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.))); + // let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.))); let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 100.)); let blit_node = BlitNode::new( FutureWrapperNode::new(ClonedNode::new(brush_texture)), diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index a38870deeb..ab9b3b66b7 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -2,7 +2,6 @@ use dyn_any::{DynFuture, StaticType}; use graph_craft::document::value::RenderOutput; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_core::fn_type; -use graphene_core::ops::IdentityNode; use graphene_core::raster::color::Color; use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::*; diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 745850d1d6..cbeda1eaa3 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -188,12 +188,12 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result + #graphene_core::WasmNotSync ) } - (ParsedField::Node { input_type, output_type, .. }, false) => { + (ParsedField::Node { input_type: _, output_type: _, .. }, false) => { unreachable!(); - quote!( - // #name: 'n, - #name: #graphene_core::Node<'n, #input_type, Output = #output_type> + #graphene_core::WasmNotSync - ) + // quote!( + // // #name: 'n, + // #name: #graphene_core::Node<'n, #input_type, Output = #output_type> + #graphene_core::WasmNotSync + // ) } (ParsedField::Node { input_type, output_type, .. }, true) => { let id = future_idents.len(); @@ -413,7 +413,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st true => parsed.input.ty.clone(), false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(), }; - let node_io = if (parsed.is_async || true) { quote!(to_async_node_io) } else { quote!(to_node_io) }; + let node_io = if parsed.is_async || true { quote!(to_async_node_io) } else { quote!(to_node_io) }; constructors.push(quote!( ( |args| { From 31e00359b79b59c70e8960b79b1d12d527f14e26 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Fri, 14 Feb 2025 11:29:42 +0100 Subject: [PATCH 27/34] Fix more things --- .../document/node_graph/document_node_definitions.rs | 4 ++++ .../messages/portfolio/document/node_graph/node_properties.rs | 2 +- node-graph/graph-craft/src/proto.rs | 3 ++- node-graph/interpreted-executor/src/node_registry.rs | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 7c19caecad..ff8ee5d5cf 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -2693,6 +2693,10 @@ fn static_nodes() -> Vec { } let output_type = &first_node_io.return_value; + if *display_name == "Text" { + log::debug!("{} {:?}", display_name, first_node_io); + } + let inputs = fields .iter() .zip(first_node_io.inputs.iter()) diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 8e137f8a74..669db9091e 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -255,7 +255,7 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, numbe } Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(), Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, context), - Type::Future(_) => vec![TextLabel::new("Future type (not supported)").widget_holder()].into(), + Type::Future(out) => return property_from_type(node_id, index, out, number_options, context), }; extra_widgets.push(widgets); extra_widgets diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 143b7c4c86..cb71106c80 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -555,9 +555,10 @@ impl core::fmt::Debug for GraphErrorType { GraphErrorType::NoConstructor => write!(f, "No construct found for node"), GraphErrorType::InvalidImplementations { inputs, error_inputs } => { let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}, found: {_found}", index + 1); - let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::>().join("\n"); + let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::>().join("\n").replace("Option>", "Context"); let mut errors = error_inputs.iter().map(format_error_list).collect::>(); errors.sort(); + let inputs = inputs.replace("Option>", "Context"); write!( f, "This node isn't compatible with the com-\n\ diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index ab9b3b66b7..cef485df59 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -247,6 +247,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Image]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]), @@ -262,6 +263,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => GraphicElement]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroup]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ShaderInputFrame]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]), From 1bd345df9504289012c28ec4d9216dcb556f8f65 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 15 Feb 2025 15:12:13 -0800 Subject: [PATCH 28/34] Fix opening demo art with manual composition upgrading --- editor/src/messages/portfolio/portfolio_message_handler.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 6afd3683f7..8c6e37266a 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -575,6 +575,7 @@ impl MessageHandler> for PortfolioMes }; let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else { + log::error!("could not get reference in deserialize_document"); continue; }; @@ -584,6 +585,11 @@ impl MessageHandler> for PortfolioMes }; let inputs_count = node.inputs.len(); + // Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition + if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { + document.network_interface.set_manual_compostion(node_id, &[], graph_craft::concrete!(graphene_std::Context).into()); + } + // Upgrade Fill nodes to the format change in #1778 if reference == "Fill" && inputs_count == 8 { let node_definition = resolve_document_node_type(reference).unwrap(); From 20ce7b35735a6f56f293b072b34701bc4fa6cd71 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Fri, 28 Feb 2025 18:44:29 +0100 Subject: [PATCH 29/34] Set correct type for manual composition --- Cargo.lock | 36 ++++++++++--------- Cargo.toml | 6 ++-- .../node_graph/document_node_definitions.rs | 14 ++++---- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e04ac9aeae..8ad8a52c9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3505,9 +3505,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -7502,20 +7502,21 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", @@ -7527,9 +7528,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -7540,9 +7541,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7550,9 +7551,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -7563,9 +7564,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" @@ -7691,9 +7695,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index c949a405ba..ac5cf7f7c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,10 +63,10 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git" } wgpu-types = "23" wgpu = "23" once_cell = "1.13" # Remove when `core::cell::LazyCell` () is stabilized in Rust 1.80 and we bump our MSRV -wasm-bindgen = "=0.2.99" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things. +wasm-bindgen = "=0.2.100" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things. wasm-bindgen-futures = "0.4" -js-sys = "=0.3.76" -web-sys = "=0.3.76" +js-sys = "=0.3.77" +web-sys = "=0.3.77" winit = "0.29" url = "2.5" tokio = { version = "1.29", features = ["fs", "io-std"] } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index ff8ee5d5cf..2d6d6dbbf7 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -398,13 +398,13 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")), ..Default::default() }, @@ -638,7 +638,7 @@ fn static_nodes() -> Vec { DocumentNode { inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), skip_deduplication: true, ..Default::default() }, @@ -651,7 +651,7 @@ fn static_nodes() -> Vec { DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), ..Default::default() }, ] @@ -1158,7 +1158,7 @@ fn static_nodes() -> Vec { }, DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::node(NodeId(0), 0)], - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UniformNode")), ..Default::default() }, @@ -2089,7 +2089,7 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), inputs: vec![ NodeInput::scope("editor-api"), NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false), @@ -2765,7 +2765,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN DocumentNode { inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 0)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), skip_deduplication: true, ..Default::default() }, From 4a5c16b50f6b05f2439d53a79d7fcbb7aca31acc Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Fri, 28 Feb 2025 20:04:10 +0100 Subject: [PATCH 30/34] Fix brush --- node-graph/gstd/src/brush.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 25665c8cc1..62a5032ad8 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -145,7 +145,7 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image { // normal_blend.eval((Color::default(), Color::default())); // use crate::raster::blend_image_tuple; // blend_image_tuple((blank_texture, stamp), &normal_blend).await.image; - crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 100.)).image + crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)).image // let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend))); // blend_executor.eval((blank_texture, stamp)).image } @@ -258,7 +258,7 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size); // let normal_blend = BlendColorPairNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal)), ValueNode::new(CopiedNode::new(100.))); - let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 100.)); + let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 1.)); let blit_node = BlitNode::new( FutureWrapperNode::new(ClonedNode::new(brush_texture)), FutureWrapperNode::new(ClonedNode::new(positions)), @@ -306,7 +306,7 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab match stroke.style.blend_mode { BlendMode::Erase => { - let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 100.)); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 1.)); let blit_node = BlitNode::new( FutureWrapperNode::new(ClonedNode::new(brush_texture)), FutureWrapperNode::new(ClonedNode::new(positions)), @@ -317,7 +317,7 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. BlendMode::Restore => { - let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 100.)); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.)); let blit_node = BlitNode::new( FutureWrapperNode::new(ClonedNode::new(brush_texture)), FutureWrapperNode::new(ClonedNode::new(positions)), @@ -330,7 +330,7 @@ async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTab } } - let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 100.)); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.)); let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params))); actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await; } From f255ed997eceff6e42e5064e955319416ddda664 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 1 Mar 2025 12:14:42 +0100 Subject: [PATCH 31/34] Fix tests --- Cargo.lock | 23 +++++ Cargo.toml | 1 + node-graph/gcore/src/ops.rs | 7 +- node-graph/gcore/src/structural.rs | 8 +- node-graph/gcore/src/vector/vector_nodes.rs | 97 +++++++++---------- node-graph/graph-craft/Cargo.toml | 1 + node-graph/graph-craft/src/document.rs | 4 +- node-graph/gstd/src/brush.rs | 4 +- node-graph/gstd/src/image_color_palette.rs | 32 +++--- .../interpreted-executor/src/node_registry.rs | 4 +- 10 files changed, 97 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ad8a52c9c..7f3657621f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,6 +1397,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -2400,6 +2406,7 @@ dependencies = [ "js-sys", "log", "num-traits", + "pretty_assertions", "reqwest 0.12.12", "rustc-hash 2.1.0", "serde", @@ -4907,6 +4914,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -8828,6 +8845,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index ac5cf7f7c2..fe7e330dbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ base64 = "0.22" image = { version = "0.25", default-features = false, features = ["png"] } rustybuzz = "0.20" spirv = "0.3" +pretty_assertions = "1.4.1" fern = { version = "0.7", features = ["colored"] } num_enum = "0.7" num-derive = "0.4" diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 691ad94910..d09de3ff2b 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -538,13 +538,13 @@ where #[cfg(test)] mod test { use super::*; - use crate::{generic::*, structural::*, value::*}; + use crate::generic::*; #[test] pub fn dot_product_function() { let vector_a = glam::DVec2::new(1., 2.); let vector_b = glam::DVec2::new(3., 4.); - assert_eq!(dot_product(vector_a, vector_b), 11.); + assert_eq!(dot_product((), vector_a, vector_b), 11.); } #[test] @@ -573,8 +573,7 @@ mod test { #[test] pub fn identity_node() { - let value = ValueNode(4u32).then(IdentityNode::new()); - assert_eq!(value.eval(()), &4); + assert_eq!(identity(&4), &4); } #[test] diff --git a/node-graph/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index be702a5b74..b4a6feaa28 100644 --- a/node-graph/gcore/src/structural.rs +++ b/node-graph/gcore/src/structural.rs @@ -132,14 +132,14 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode { #[cfg(test)] mod test { use super::*; - use crate::{ops::IdentityNode, value::ValueNode}; + use crate::{generic::FnNode, value::ValueNode}; #[test] fn compose() { let value = ValueNode::new(4u32); - let compose = value.then(IdentityNode::new()); + let compose = value.then(FnNode::new(|x| x)); assert_eq!(compose.eval(()), &4u32); - let type_erased = &compose as &dyn for<'i> Node<'i, (), Output = &'i u32>; + let type_erased = &compose as &dyn Node<'_, (), Output = &'_ u32>; assert_eq!(type_erased.eval(()), &4u32); } @@ -148,7 +148,7 @@ mod test { let value = ValueNode::new(5); assert_eq!(value.eval(()), &5); - let id = IdentityNode::new(); + let id = FnNode::new(|x| x); let compose = ComposeNode::new(&value, &id); diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 3ce6dad39e..1201e8818a 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1037,16 +1037,16 @@ mod test { } } - fn vector_node(data: Subpath) -> FutureWrapperNode { - FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data))) + fn vector_node(data: Subpath) -> VectorDataTable { + VectorDataTable::new(VectorData::from_subpath(data)) } #[tokio::test] async fn repeat() { let direction = DVec2::X * 1.5; let instances = 3; - let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; - let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await; + let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; + let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = vector_data.one_item(); assert_eq!(vector_data.region_bezier_paths().count(), 3); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { @@ -1057,8 +1057,8 @@ mod test { async fn repeat_transform_position() { let direction = DVec2::new(12., 10.); let instances = 8; - let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; - let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await; + let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; + let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = vector_data.one_item(); assert_eq!(vector_data.region_bezier_paths().count(), 8); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { @@ -1067,8 +1067,8 @@ mod test { } #[tokio::test] async fn circle_repeat() { - let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; - let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await; + let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; + let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = vector_data.one_item(); assert_eq!(vector_data.region_bezier_paths().count(), 8); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { @@ -1080,10 +1080,7 @@ mod test { } #[tokio::test] async fn bounding_box() { - let bounding_box = BoundingBoxNode { - vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), - }; - let bounding_box = bounding_box.eval(Footprint::default()).await; + let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await; let bounding_box = bounding_box.one_item(); assert_eq!(bounding_box.region_bezier_paths().count(), 1); let subpath = bounding_box.region_bezier_paths().next().unwrap().1; @@ -1109,8 +1106,8 @@ mod test { let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.); let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE); let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec(); - let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await; - let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await; + let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await; + let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), copy_to_points).await; let flattened_copy_to_points = flattened_copy_to_points.one_item(); assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len()); for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() { @@ -1124,7 +1121,7 @@ mod test { #[tokio::test] async fn sample_points() { let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); - let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 30., 0., 0., false, &FutureWrapperNode(vec![100.])).await; + let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await; let sample_points = sample_points.one_item(); assert_eq!(sample_points.point_domain.positions().len(), 4); for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) { @@ -1134,7 +1131,7 @@ mod test { #[tokio::test] async fn adaptive_spacing() { let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); - let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 18., 45., 10., true, &FutureWrapperNode(vec![100.])).await; + let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await; let sample_points = sample_points.one_item(); assert_eq!(sample_points.point_domain.positions().len(), 4); for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) { @@ -1145,7 +1142,7 @@ mod test { async fn poisson() { let sample_points = super::poisson_disk_points( Footprint::default(), - &vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)), + vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)), 10. * std::f64::consts::SQRT_2, 0, ) @@ -1163,12 +1160,12 @@ mod test { #[tokio::test] async fn lengths() { let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); - let lengths = subpath_segment_lengths(Footprint::default(), &vector_node(subpath)).await; + let lengths = subpath_segment_lengths(Footprint::default(), vector_node(subpath)).await; assert_eq!(lengths, vec![100.]); } #[tokio::test] async fn spline() { - let spline = super::spline(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await; + let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await; let spline = spline.one_item(); assert_eq!(spline.stroke_bezier_paths().count(), 1); assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]); @@ -1177,7 +1174,7 @@ mod test { async fn morph() { let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO); - let sample_points = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await; + let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await; let sample_points = sample_points.one_item(); assert_eq!( &sample_points.point_domain.positions()[..4], @@ -1186,7 +1183,7 @@ mod test { } #[track_caller] - fn contains_segment(vector: &VectorData, target: bezier_rs::Bezier) { + fn contains_segment(vector: VectorData, target: bezier_rs::Bezier) { let segments = vector.segment_bezier_iter().map(|x| x.1); let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count(); assert_eq!(count, 1, "Incorrect number of {target:#?} in {:#?}", vector.segment_bezier_iter().collect::>()); @@ -1195,42 +1192,42 @@ mod test { #[tokio::test] async fn bevel_rect() { let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); - let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await; + let beveled = super::bevel(Footprint::default(), vector_node(source), 5.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 8); assert_eq!(beveled.segment_domain.ids().len(), 8); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(95., 0.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(95., 100.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 5.), DVec2::new(0., 95.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(95., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(95., 100.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 5.), DVec2::new(0., 95.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.))); // Joins - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(0., 5.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(95., 0.), DVec2::new(100., 5.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 95.), DVec2::new(95., 100.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(0., 5.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(95., 0.), DVec2::new(100., 5.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 95.), DVec2::new(95., 100.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.))); } #[tokio::test] async fn bevel_open_curve() { let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.); let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false); - let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await; + let beveled = super::bevel((), vector_node(source), 5.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.segment_domain.ids().len(), 3); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-100., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-100., 0.))); let trimmed = curve.trim(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.)); - contains_segment(&beveled, trimmed); + contains_segment(beveled.clone(), trimmed); // Join - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start)); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start)); } #[tokio::test] @@ -1240,7 +1237,7 @@ mod test { let mut vector_data = VectorData::from_subpath(source); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)); vector_data.transform = transform; - let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(VectorDataTable::new(vector_data)), 5.).await; + let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 4); @@ -1248,31 +1245,31 @@ mod test { assert_eq!(beveled.transform, transform); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.))); let trimmed = curve.trim(bezier_rs::TValue::Euclidean(0.5 / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.)); - contains_segment(&beveled, trimmed); + contains_segment(beveled.clone(), trimmed); // Join - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start)); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start)); } #[tokio::test] async fn bevel_too_high() { let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false); - let beveled = super::bevel(Footprint::default(), &vector_node(source), 999.).await; + let beveled = super::bevel(Footprint::default(), vector_node(source), 999.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.segment_domain.ids().len(), 5); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 0.), DVec2::new(50., 0.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(100., 50.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 0.), DVec2::new(50., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(100., 50.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.))); // Joins - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(50., 0.), DVec2::new(100., 50.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(50., 0.), DVec2::new(100., 50.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.))); } #[tokio::test] @@ -1280,18 +1277,18 @@ mod test { let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.); let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO); let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false); - let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await; + let beveled = super::bevel(Footprint::default(), vector_node(source), 5.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.segment_domain.ids().len(), 5); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-100., 0.), DVec2::new(-5., 0.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(0., 0.))); - contains_segment(&beveled, point); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-100., 0.), DVec2::new(-5., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(0., 0.))); + contains_segment(beveled.clone(), point); let [start, end] = curve.split(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001)))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(start.start, start.end)); - contains_segment(&beveled, end); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(start.start, start.end)); + contains_segment(beveled.clone(), end); } } diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index 107ef5e413..b876af99b3 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -54,6 +54,7 @@ winit = { workspace = true } [dev-dependencies] # Workspace dependencies graph-craft = { workspace = true, features = ["loading"] } +pretty_assertions = { workspace = true } # Required dependencies criterion = { version = "0.5", features = ["html_reports"]} diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 924a626639..65d1af8c5a 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -1567,7 +1567,7 @@ mod test { NodeId(14), ProtoNode { identifier: "graphene_core::value::ClonedNode".into(), - input: ProtoNodeInput::None, + input: ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context)), construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()), original_location: OriginalLocation { path: Some(vec![NodeId(1), NodeId(4)]), @@ -1589,7 +1589,7 @@ mod test { println!("{:#?}", resolved_network[0]); println!("{construction_network:#?}"); - assert_eq!(resolved_network[0], construction_network); + pretty_assertions::assert_eq!(resolved_network[0], construction_network); } fn flat_network() -> NodeNetwork { diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 62a5032ad8..7017b73c99 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -343,15 +343,13 @@ mod test { use super::*; use graphene_core::transform::Transform; - use graphene_core::value::ClonedNode; use glam::DAffine2; #[test] fn test_brush_texture() { - let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.)); let size = 20.; - let image = brush_texture_node.eval(size); + let image = brush_stamp_generator(size, Color::BLACK, 100., 100.); assert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.))); // center pixel should be BLACK assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK)); diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index 1f2b040fdf..a554c9ee15 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -65,30 +65,24 @@ async fn image_color_palette( mod test { use super::*; - use graph_craft::generic::FnNode; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::Image; - use graphene_core::value::CopiedNode; - use graphene_core::Node; #[test] fn test_image_color_palette() { - let node = ImageColorPaletteNode { - max_size: CopiedNode(1u32), - image: FnNode::new(|_| { - Box::pin(async move { - ImageFrameTable::new(ImageFrame { - image: Image { - width: 100, - height: 100, - data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], - base64_string: None, - }, - ..Default::default() - }) - }) + let result = image_color_palette( + (), + ImageFrameTable::new(ImageFrame { + image: Image { + width: 100, + height: 100, + data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], + base64_string: None, + }, + ..Default::default() }), - }; - assert_eq!(futures::executor::block_on(node.eval(())), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]); + 1, + ); + assert_eq!(futures::executor::block_on(result), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]); } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index cef485df59..85ab9d23b0 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,4 +1,4 @@ -use dyn_any::{DynFuture, StaticType}; +use dyn_any::StaticType; use graph_craft::document::value::RenderOutput; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_core::fn_type; @@ -96,7 +96,7 @@ fn node_registry() -> HashMap>::new(graphene_std::any::PanicNode::>::new()); + let node = >::new(graphene_std::any::PanicNode::>::new()); let params = vec![fn_type_fut!(Context, &WasmEditorApi)]; let mut node_io = as NodeIO<'_, Context>>::to_async_node_io(&node, params); node_io.call_argument = concrete!(::Static); From 5266358704ff3eb307143f8d27191f120f161f43 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 1 Mar 2025 05:24:36 -0800 Subject: [PATCH 32/34] Update docs for deps --- .devcontainer/devcontainer.json | 2 +- website/content/volunteer/guide/project-setup/_index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 18c54c412e..6b4ee383f2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ }, "ghcr.io/devcontainers/features/node:1": {} }, - "onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.99", + "onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.100", "customizations": { "vscode": { // NOTE: Keep this in sync with `.vscode/extensions.json` diff --git a/website/content/volunteer/guide/project-setup/_index.md b/website/content/volunteer/guide/project-setup/_index.md index 9da71b5c1e..a40c6244b6 100644 --- a/website/content/volunteer/guide/project-setup/_index.md +++ b/website/content/volunteer/guide/project-setup/_index.md @@ -21,7 +21,7 @@ Next, install the dependencies required for development builds: ```sh cargo install cargo-watch cargo install wasm-pack -cargo install -f wasm-bindgen-cli@0.2.99 +cargo install -f wasm-bindgen-cli@0.2.100 ``` Regarding the last one: you'll likely get faster build times if you manually install that specific version of `wasm-bindgen-cli`. It is supposed to be installed automatically but a version mismatch causes it to reinstall every single recompilation. It may need to be manually updated periodically to match the version of the `wasm-bindgen` dependency in [`Cargo.toml`](https://github.com/GraphiteEditor/Graphite/blob/master/Cargo.toml). From a9395ceb0441efdca3001e4bfbac8a1fc732f99a Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 1 Mar 2025 13:27:24 -0800 Subject: [PATCH 33/34] Fix up some node signature issues --- .../node_graph/document_node_definitions.rs | 49 ++++++++++--------- node-graph/gcore/src/ops.rs | 2 +- node-graph/gcore/src/raster/adjustments.rs | 2 +- node-graph/gstd/src/dehaze.rs | 2 +- node-graph/gstd/src/raster.rs | 4 +- node-graph/gstd/src/wasm_application_io.rs | 4 +- node-graph/interpreted-executor/src/util.rs | 2 +- 7 files changed, 33 insertions(+), 32 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 5e8db6624f..90542fd4c0 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -390,7 +390,7 @@ fn static_nodes() -> Vec { }, DocumentNodeDefinition { identifier: "Load Image", - category: "Raster: Generator", + category: "Network", node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { @@ -1947,28 +1947,29 @@ fn static_nodes() -> Vec { // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files - DocumentNodeDefinition { - identifier: "Curves", - category: "Raster: Adjustment", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"), - inputs: vec![ - NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), - NodeInput::value(TaggedValue::Curve(Default::default()), false), - ], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Image".into(), "Curve".into()], - output_names: vec!["Image".to_string()], - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, - (*IMAGINATE_NODE).clone(), + // TODO: Fix this, it's currently broken + // DocumentNodeDefinition { + // identifier: "Curves", + // category: "Raster: Adjustment", + // node_template: NodeTemplate { + // document_node: DocumentNode { + // implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"), + // inputs: vec![ + // NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true), + // NodeInput::value(TaggedValue::Curve(Default::default()), false), + // ], + // ..Default::default() + // }, + // persistent_node_metadata: DocumentNodePersistentMetadata { + // input_properties: vec!["Image".into(), "Curve".into()], + // output_names: vec!["Image".to_string()], + // ..Default::default() + // }, + // }, + // description: Cow::Borrowed("TODO"), + // properties: None, + // }, + // (*IMAGINATE_NODE).clone(), DocumentNodeDefinition { identifier: "Line", category: "Vector: Shape", @@ -2757,7 +2758,7 @@ fn static_nodes() -> Vec { pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentNodeDefinition { identifier: "Imaginate", - category: "Raster: Generator", + category: "Raster", node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index d09de3ff2b..a44f379eea 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -429,7 +429,7 @@ fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 { } /// Constructs a two-dimensional vector value which may be set to any XY coordinate. -#[node_macro::node(category("Value"))] +#[node_macro::node(name("Vector2 Value"), category("Value"))] fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 { DVec2::new(x, y) } diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 80d6053757..9e875a8f5a 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -1236,7 +1236,7 @@ impl core::fmt::Display for SelectiveColorChoice { // // Algorithm based on: // https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html -#[node_macro::node(category("Raster: Adjustment"))] +#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"))] async fn selective_color>( _: impl Ctx, #[implementations( diff --git a/node-graph/gstd/src/dehaze.rs b/node-graph/gstd/src/dehaze.rs index 65af328630..20d942d2f7 100644 --- a/node-graph/gstd/src/dehaze.rs +++ b/node-graph/gstd/src/dehaze.rs @@ -7,7 +7,7 @@ use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer use ndarray::{Array2, ArrayBase, Dim, OwnedRepr}; use std::cmp::{max, min}; -#[node_macro::node(category("Raster: Filter"))] +#[node_macro::node(category("Raster"))] async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable, strength: Percentage) -> ImageFrameTable { let image_frame = image_frame.one_item(); diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 74135fa3c5..39cbf95efe 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -472,7 +472,7 @@ fn empty_image(_: impl Ctx, transform: DAffine2, #[implementations(Col // tiling: Tiling: bool, // } -#[node_macro::node(category("Raster: Generator"))] +#[node_macro::node(category("Raster"))] #[allow(clippy::too_many_arguments)] fn noise_pattern( ctx: impl ExtractFootprint + Ctx, @@ -627,7 +627,7 @@ fn noise_pattern( ImageFrameTable::new(result) } -#[node_macro::node(category("Raster: Generator"))] +#[node_macro::node(category("Raster"))] fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable { let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index eaea9e3f55..c3231ea4a0 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -59,7 +59,7 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> { +async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> { let Some(api) = editor.application_io.as_ref() else { return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); }; @@ -73,7 +73,7 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] data } -#[node_macro::node(category("Raster"))] +#[node_macro::node(category("Network"))] fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable { let Some(image) = image::load_from_memory(data.as_ref()).ok() else { return ImageFrameTable::default(); diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index f419def798..1b11f667c0 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -27,7 +27,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc Date: Sat, 1 Mar 2025 14:43:53 -0800 Subject: [PATCH 34/34] Code review --- .../node_graph/document_node_definitions.rs | 4 - node-graph/gcore/src/context.rs | 4 +- node-graph/gcore/src/logic.rs | 2 +- node-graph/gcore/src/structural.rs | 3 +- node-graph/interpreted-executor/src/util.rs | 15 ++-- node-graph/node-macro/src/codegen.rs | 82 ++++++------------- 6 files changed, 35 insertions(+), 75 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 90542fd4c0..848807322c 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -2694,10 +2694,6 @@ fn static_nodes() -> Vec { } let output_type = &first_node_io.return_value; - if *display_name == "Text" { - log::debug!("{} {:?}", display_name, first_node_io); - } - let inputs = fields .iter() .zip(first_node_io.inputs.iter()) diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index cf86e9f6c4..3e21d722af 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -1,8 +1,8 @@ +use crate::transform::Footprint; + use core::{any::Any, borrow::Borrow, panic::Location}; use std::sync::Arc; -use crate::transform::Footprint; - pub trait Ctx: Clone + Send {} pub trait ExtractFootprint { diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 0e4214f060..2abdc723ee 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -32,7 +32,7 @@ async fn switch( if_false: impl Node, ) -> T { if condition { - // we can't remove these calls because we only want to evaluate the brach that we actually need + // We can't remove these calls because we only want to evaluate the branch that we actually need if_true.eval(ctx).await } else { if_false.eval(ctx).await diff --git a/node-graph/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index b4a6feaa28..5fc7c5da6a 100644 --- a/node-graph/gcore/src/structural.rs +++ b/node-graph/gcore/src/structural.rs @@ -132,7 +132,8 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode { #[cfg(test)] mod test { use super::*; - use crate::{generic::FnNode, value::ValueNode}; + use crate::generic::FnNode; + use crate::value::ValueNode; #[test] fn compose() { diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index 1b11f667c0..4e88b41a5e 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -1,13 +1,12 @@ use std::sync::Arc; -use graph_craft::{ - concrete, - document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}, - generic, - wasm_application_io::WasmEditorApi, - ProtoNodeIdentifier, -}; -use graphene_std::{uuid::NodeId, Context}; +use graph_craft::concrete; +use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}; +use graph_craft::generic; +use graph_craft::wasm_application_io::WasmEditorApi; +use graph_craft::ProtoNodeIdentifier; +use graphene_std::uuid::NodeId; +use graphene_std::Context; // TODO: this is copy pasta from the editor (and does get out of sync) pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc) -> NodeNetwork { diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 852aa93b34..7bc6148ce3 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -84,7 +84,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result ty.clone(), ParsedField::Node { output_type, input_type, .. } => match parsed.is_async { - true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = impl core::future::Future > ), + true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = impl core::future::Future>), false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>), }, }) @@ -166,7 +166,6 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let name = &pat_ident.ident; quote! { let #name = self.#name.eval(__input.clone()).await; } - // quote! { let #name = self.#name.eval(()); } } ParsedField::Node { pat_ident, .. } => { let name = &pat_ident.ident; @@ -195,17 +194,9 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result + #graphene_core::WasmNotSend + 'n, for<'all> #all_lifetime_ty: #graphene_core::WasmNotSend, - // #name: 'n, #name: #graphene_core::Node<'n, #input_type, Output = #fut_ident> + #graphene_core::WasmNotSync ) } - (ParsedField::Node { input_type: _, output_type: _, .. }, false) => { - unreachable!(); - // quote!( - // // #name: 'n, - // #name: #graphene_core::Node<'n, #input_type, Output = #output_type> + #graphene_core::WasmNotSync - // ) - } (ParsedField::Node { input_type, output_type, .. }, true) => { let id = future_idents.len(); let fut_ident = format_ident!("F{}", id); @@ -213,10 +204,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result + #graphene_core::WasmNotSend + 'n, - // #name: Send + Sync + 'n, #name: #graphene_core::Node<'n, #input_type, Output = #fut_ident > + #graphene_core::WasmNotSync ) } + (ParsedField::Node { .. }, false) => unreachable!(), }); } let where_clause = where_clause.clone().unwrap_or(WhereClause { @@ -238,25 +229,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result; - #[inline] - fn eval(&'n self, __input: #input_type) -> Self::Output { - Box::pin(async move { - #(#eval_args)* - self::#fn_name(__input #(, #field_names)*) #await_keyword - }) - } - } - } else { - quote! { - type Output = #output_type; - #[inline] - fn eval(&'n self, __input: #input_type) -> Self::Output { + let eval_impl = quote! { + type Output = #graphene_core::registry::DynFuture<'n, #output_type>; + #[inline] + fn eval(&'n self, __input: #input_type) -> Self::Output { + Box::pin(async move { #(#eval_args)* - self::#fn_name(__input #(, #field_names)*) - } + self::#fn_name(__input #(, #field_names)*) #await_keyword + }) } }; let path = match parsed.attributes.path { @@ -360,9 +340,9 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st match field { ParsedField::Regular { implementations, ty, .. } => { if !implementations.is_empty() { - implementations.iter().map(|ty| (&unit, ty, false)).collect() + implementations.iter().map(|ty| (&unit, ty)).collect() } else { - vec![(&unit, ty, false)] + vec![(&unit, ty)] } } ParsedField::Node { @@ -372,20 +352,19 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st .. } => { if !implementations.is_empty() { - implementations.iter().map(|impl_| (&impl_.input, &impl_.output, true)).collect() + implementations.iter().map(|impl_| (&impl_.input, &impl_.output)).collect() } else { - vec![(input_type, output_type, true)] + vec![(input_type, output_type)] } } } .into_iter() - .map(|(input, out, node)| (substitute_lifetimes(input.clone(), "_"), substitute_lifetimes(out.clone(), "_"), node)) + .map(|(input, out)| (substitute_lifetimes(input.clone(), "_"), substitute_lifetimes(out.clone(), "_"))) .collect::>() }) .collect(); let max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max(); - let future_node = (!parsed.is_async && false).then(|| quote!(let node = gcore::registry::FutureWrapperNode::new(node);)); for i in 0..max_implementations.unwrap_or(0) { let mut temp_constructors = Vec::new(); @@ -394,38 +373,24 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st for (j, types) in parameter_types.iter().enumerate() { let field_name = field_names[j]; - let (input_type, output_type, impl_node) = &types[i.min(types.len() - 1)]; + let (input_type, output_type) = &types[i.min(types.len() - 1)]; let node = matches!(parsed.fields[j], ParsedField::Node { .. }); let downcast_node = quote!( - let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone()); - ); - temp_constructors.push(if node { - if !parsed.is_async { - return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters")); - } - downcast_node - } else { - quote!( - #downcast_node - // let #field_name = #field_name.eval(None).await; - // let #field_name = ClonedNode::new(#field_name); - // let #field_name: TypeNode<_, #input_type, #output_type> = TypeNode::new(#field_name); - // try polling futures - ) - }); + let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone()); + ); + if node && !parsed.is_async { + return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters")); + } + temp_constructors.push(downcast_node); temp_node_io.push(quote!(fn_type_fut!(#input_type, #output_type, alias: #output_type))); - match (parsed.is_async || true) && *impl_node || true { - true => panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)), - false => panic_node_types.push(quote!(#input_type, #output_type)), - }; + panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)); } let input_type = match parsed.input.implementations.is_empty() { true => parsed.input.ty.clone(), false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(), }; - let node_io = if parsed.is_async || true { quote!(to_async_node_io) } else { quote!(to_node_io) }; constructors.push(quote!( ( |args| { @@ -433,14 +398,13 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st #(#temp_constructors;)* let node = #struct_name::new(#(#field_names,)*); // try polling futures - #future_node let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node); Box::new(any) as TypeErasedBox<'_> }) }, { let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*); let params = vec![#(#temp_node_io,)*]; - let mut node_io = NodeIO::<'_, #input_type>::#node_io(&node, params); + let mut node_io = NodeIO::<'_, #input_type>::to_async_node_io(&node, params); node_io }