From 71f12db1e645c8baf54ed64e6ad1a626aa40a802 Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 22 Aug 2022 17:18:26 +0200 Subject: [PATCH] Impl DynNode --- Cargo.lock | 40 +++--- libraries/dyn-any/src/lib.rs | 49 +++++--- node-graph/borrow_stack/src/lib.rs | 7 ++ node-graph/gcore/Cargo.toml | 2 +- node-graph/gcore/src/lib.rs | 48 +++++-- node-graph/gcore/src/ops.rs | 26 +++- node-graph/gcore/src/raster.rs | 8 +- node-graph/gcore/src/structural.rs | 54 ++++++-- node-graph/gstd/Cargo.toml | 2 +- node-graph/gstd/src/any.rs | 161 ++++++++++++++++++++++++ node-graph/gstd/src/document.rs | 94 ++++++++++++++ node-graph/gstd/src/lib.rs | 16 +-- node-graph/gstd/src/main.rs | 1 + node-graph/gstd/src/memo.rs | 1 - node-graph/gstd/src/raster.rs | 8 +- node-graph/gstd/test-image-1-result.png | Bin 0 -> 29388 bytes 16 files changed, 433 insertions(+), 84 deletions(-) create mode 100644 node-graph/gstd/src/any.rs create mode 100644 node-graph/gstd/src/document.rs create mode 100644 node-graph/gstd/test-image-1-result.png diff --git a/Cargo.lock b/Cargo.lock index a5eb3749..31197f1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "env_logger" @@ -253,16 +253,15 @@ dependencies = [ [[package]] name = "exr" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cc0e06fb5f67e5d6beadf3a382fec9baca1aa751c6d5368fdeee7e5932c215" +checksum = "78c26a90d9dd411a3d119d6f55752fb4c134ca243250c32fb9cab7b2561638d2" dependencies = [ "bit_field", - "deflate", "flume", "half", - "inflate", "lebe", + "miniz_oxide", "smallvec", "threadpool", ] @@ -298,15 +297,15 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" [[package]] name = "getrandom" @@ -479,15 +478,6 @@ dependencies = [ "tiff", ] -[[package]] -name = "inflate" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" -dependencies = [ - "adler32", -] - [[package]] name = "itoa" version = "1.0.1" @@ -529,9 +519,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lebe" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" @@ -646,18 +636,18 @@ checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index e0e10c50..985e1906 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -25,6 +25,15 @@ pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> { None } } +pub fn downcast<'a, V: StaticType>(i: Box + 'a>) -> Option> { + if i.type_id() == std::any::TypeId::of::<::Static>() { + // SAFETY: caller guarantees that T is the correct type + let ptr = Box::into_raw(i) as *mut dyn DynAny<'a> as *mut V; + Some(unsafe { Box::from_raw(ptr) }) + } else { + None + } +} pub trait StaticType { type Static: 'static + ?Sized; @@ -39,8 +48,11 @@ pub trait StaticTypeSized { std::any::TypeId::of::() } } -impl<'a, T: StaticTypeSized> StaticType for T { - type Static = ::Static; +impl StaticTypeSized for T +where + T::Static: Sized, +{ + type Static = ::Static; } pub trait StaticTypeClone { type Static: 'static + Clone; @@ -48,41 +60,44 @@ pub trait StaticTypeClone { std::any::TypeId::of::() } } -impl<'a, T: StaticTypeClone> StaticTypeSized for T { - type Static = ::Static; +impl StaticTypeClone for T +where + T::Static: Clone, +{ + type Static = ::Static; } macro_rules! impl_type { ($($id:ident$(<$($(($l:lifetime, $s:lifetime)),*|)?$($T:ident),*>)?),*) => { $( - impl<'a, $($($T: 'a + $crate::StaticTypeSized + Sized,)*)?> $crate::StaticTypeSized for $id $(<$($($l,)*)?$($T, )*>)?{ + impl< $($($T: $crate::StaticTypeSized ,)*)?> $crate::StaticType for $id $(<$($($l,)*)?$($T, )*>)?{ type Static = $id$(<$($($s,)*)?$(<$T as $crate::StaticTypeSized>::Static,)*>)?; } )* }; } -impl<'a, T: Clone + StaticTypeClone> StaticTypeClone for std::borrow::Cow<'a, T> { - type Static = std::borrow::Cow<'static, ::Static>; +impl<'a, T: StaticTypeClone + Clone> StaticType for std::borrow::Cow<'a, T> { + type Static = std::borrow::Cow<'static, T::Static>; } -impl<'a, T: StaticTypeSized> StaticTypeSized for *const [T] { +impl StaticType for *const [T] { type Static = *const [::Static]; } -impl<'a, T: StaticTypeSized> StaticTypeSized for *mut [T] { +impl StaticType for *mut [T] { type Static = *mut [::Static]; } -impl<'a, T: StaticTypeSized> StaticTypeSized for &'a [T] { +impl<'a, T: StaticTypeSized> StaticType for &'a [T] { type Static = &'static [::Static]; } -impl<'a> StaticTypeSized for &'a str { +impl<'a> StaticType for &'a str { type Static = &'static str; } -impl<'a> StaticTypeSized for () { +impl StaticType for () { type Static = (); } -impl<'a, T: 'a + StaticTypeClone> StaticTypeClone for &'a T { - type Static = &'static ::Static; +impl<'a, T: 'a + StaticType> StaticType for &'a T { + type Static = &'static ::Static; } -impl<'a, T: StaticTypeSized, const N: usize> StaticTypeSized for [T; N] { +impl StaticType for [T; N] { type Static = [::Static; N]; } @@ -102,7 +117,7 @@ use std::{ impl_type!(Option,Result,Cell,UnsafeCell,RefCell,MaybeUninit, Vec, String, BTreeMap,BTreeSet, LinkedList, VecDeque, - BinaryHeap, ManuallyDrop, PhantomData, PhantomPinned,Empty, + BinaryHeap, Box, ManuallyDrop, PhantomData, PhantomPinned,Empty, Wrapping, Duration, Once, Mutex, RwLock, bool, f32, f64, char, u8, AtomicU8, u16,AtomicU16, u32,AtomicU32, u64,AtomicU64, usize,AtomicUsize, i8,AtomicI8, i16,AtomicI16, i32,AtomicI32, i64,AtomicI64, isize,AtomicIsize, @@ -115,7 +130,7 @@ macro_rules! impl_tuple { impl_tuple! { @rec $($t)* } }; (@impl $($t:ident)*) => { - impl<'dyn_any, $($t: StaticTypeSized,)*> StaticTypeSized for ($($t,)*) { + impl< $($t: StaticTypeSized,)*> StaticType for ($($t,)*) { type Static = ($(<$t as $crate::StaticTypeSized>::Static,)*); } }; diff --git a/node-graph/borrow_stack/src/lib.rs b/node-graph/borrow_stack/src/lib.rs index 5026607a..90fcaf4f 100644 --- a/node-graph/borrow_stack/src/lib.rs +++ b/node-graph/borrow_stack/src/lib.rs @@ -7,8 +7,11 @@ use std::{ pub trait BorrowStack<'n> { type Item; + /// # Safety unsafe fn push(&'n self, value: Self::Item); + /// # Safety unsafe fn pop(&'n self); + /// # Safety unsafe fn get(&'n self) -> &'n [Self::Item]; } @@ -37,6 +40,10 @@ impl<'n, T: Unpin> FixedSizeStack<'n, T> { pub fn len(&self) -> usize { self.len.load(Ordering::SeqCst) } + + pub fn is_empty(&self) -> bool { + self.len.load(Ordering::SeqCst) == 0 + } } impl<'n, T> BorrowStack<'n> for FixedSizeStack<'n, T> { diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index 56e43bbc..79415975 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" [features] std = ["dyn-any"] -default = ["gpu", "async"] +default = ["async"] gpu = ["spirv-std"] async = ["async-trait"] nightly = [] diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 50c0f531..6f34c850 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -3,6 +3,7 @@ #[cfg(feature = "async")] extern crate alloc; + #[cfg(feature = "async")] use alloc::boxed::Box; #[cfg(feature = "async")] @@ -18,12 +19,35 @@ pub trait Node { type Output; fn eval(self, input: T) -> Self::Output; + fn input(&self) -> &str { + core::any::type_name::() + } + fn output(&self) -> &str { + core::any::type_name::() + } } trait Input { unsafe fn input(&self, input: I); } +pub trait RefNode { + type Output; + + fn eval_ref(&self, input: T) -> Self::Output; +} + +impl<'n, N: 'n, I> RefNode for &'n N +where + &'n N: Node, + Self: 'n, +{ + type Output = <&'n N as Node>::Output; + fn eval_ref(&self, input: I) -> Self::Output { + self.eval(input) + } +} + #[cfg(feature = "async")] #[async_trait] pub trait AsyncNode { @@ -46,13 +70,23 @@ pub trait Cache { fn clear(&mut self); } -#[cfg(not(feature = "gpu"))] -extern crate alloc; -#[cfg(not(feature = "gpu"))] -impl<'n, I, O: 'n> Node<'n, I> for alloc::boxed::Box> { - type Output = O; - - fn eval(&'n self, input: &'n I) -> Self::Output { +#[cfg(feature = "async")] +impl Node for Box +where + N: Node, +{ + type Output = >::Output; + fn eval(self, input: I) -> Self::Output { + (*self).eval(input) + } +} +#[cfg(feature = "async")] +impl<'n, N, I> Node for &'n Box +where + &'n N: Node, +{ + type Output = <&'n N as Node>::Output; + fn eval(self, input: I) -> Self::Output { self.as_ref().eval(input) } } diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 9d2bd7e0..3aca9eba 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -11,6 +11,24 @@ impl<'n, L: Add + 'n, R, O: 'n> Node<(L, R)> for AddNode { input.0 + input.1 } } +impl<'n, L: Add + 'n, R, O: 'n> Node<(L, R)> for &'n AddNode { + type Output = >::Output; + fn eval(self, input: (L, R)) -> Self::Output { + input.0 + input.1 + } +} +impl<'n, L: Add + 'n + Copy, R: Copy, O: 'n> Node<&'n (L, R)> for AddNode { + type Output = >::Output; + fn eval(self, input: &'n (L, R)) -> Self::Output { + input.0 + input.1 + } +} +impl<'n, L: Add + 'n + Copy, R: Copy, O: 'n> Node<&'n (L, R)> for &'n AddNode { + type Output = >::Output; + fn eval(self, input: &'n (L, R)) -> Self::Output { + input.0 + input.1 + } +} #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct CloneNode; @@ -82,7 +100,7 @@ pub struct DupNode; impl<'n, T: Clone + 'n> Node for DupNode { type Output = (T, T); fn eval(self, input: T) -> Self::Output { - (input.clone(), input) //TODO: use Copy/Clone implementation + (input.clone(), input) } } @@ -95,6 +113,12 @@ impl<'n, T: 'n> Node for IdNode { input } } +impl<'n, T: 'n> Node for &'n IdNode { + type Output = T; + fn eval(self, input: T) -> Self::Output { + input + } +} pub struct MapResultNode(pub MN, pub PhantomData<(I, E)>); diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 5139255e..7772ddb0 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -34,7 +34,7 @@ where } } -pub struct MutWrapper(pub N); +/*pub struct MutWrapper(pub N); impl<'n, T: Clone, N> Node<&'n mut T> for &'n MutWrapper where @@ -44,7 +44,7 @@ where fn eval(self, value: &'n mut T) { *value = (&self.0).eval(value.clone()); } -} +}*/ #[cfg(test)] mod test { @@ -54,8 +54,8 @@ mod test { fn map_node() { let array = &mut [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()]; (&GrayscaleNode).eval(Color::from_rgbf32_unchecked(1., 0., 0.)); - let map = ForEachNode(MutWrapper(GrayscaleNode)); + /*let map = ForEachNode(MutWrapper(GrayscaleNode)); (&map).eval(array.iter_mut()); - assert_eq!(array[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap()); + assert_eq!(array[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());*/ } } diff --git a/node-graph/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index 4db0131c..4dcd7f65 100644 --- a/node-graph/gcore/src/structural.rs +++ b/node-graph/gcore/src/structural.rs @@ -1,7 +1,8 @@ use core::marker::PhantomData; -use crate::Node; +use crate::{Node, RefNode}; +#[derive(Debug)] pub struct ComposeNode { first: First, second: Second, @@ -25,25 +26,40 @@ where } impl<'n, Input, Inter, First, Second> Node for &'n ComposeNode where - First: Node + Copy, - Second: Node + Copy, + First: RefNode + Copy, + Second: RefNode + Copy, { - type Output = Second::Output; + type Output = >::Output; fn eval(self, input: Input) -> Self::Output { // evaluate the first node with the given input // and then pipe the result from the first computation // into the second node - let arg: Inter = self.first.eval(input); - (&self.second).eval(arg) + let arg: Inter = (self.first).eval_ref(input); + (self.second).eval_ref(arg) } } - -impl<'n, Input, First: 'n, Second: 'n> ComposeNode +impl RefNode for ComposeNode where - First: Node, - Second: Node, + First: RefNode + Copy, + Second: RefNode + Copy, { + type Output = >::Output; + + fn eval_ref(&self, input: Input) -> Self::Output { + // evaluate the first node with the given input + // and then pipe the result from the first computation + // into the second node + let arg: Inter = (self.first).eval_ref(input); + (self.second).eval_ref(arg) + } +} +#[cfg(feature = "std")] +impl dyn_any::StaticType for ComposeNode { + type Static = ComposeNode; +} + +impl<'n, Input, First: 'n, Second: 'n> ComposeNode { pub const fn new(first: First, second: Second) -> Self { ComposeNode:: { first, second, _phantom: PhantomData } } @@ -79,6 +95,24 @@ pub trait AfterRef: Sized { } impl<'n, Second: 'n, I> AfterRef for Second where &'n Second: Node {} +#[cfg(feature = "async")] +pub trait AfterBox { + fn after<'n, First: 'n, Input>(self, first: First) -> ComposeNode + where + First: Node + Copy, + alloc::boxed::Box: Node, + Self: Sized, + { + ComposeNode:: { + first, + second: self, + _phantom: PhantomData, + } + } +} +#[cfg(feature = "async")] +impl<'n, Second: 'n, I> AfterBox for alloc::boxed::Box where &'n alloc::boxed::Box: Node {} + pub struct ConsNode(pub Root); impl Node for ConsNode diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index f37ada4e..82db125e 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -15,7 +15,7 @@ default = ["derive", "memoization"] [dependencies] -graphene-core = {path = "../gcore"} +graphene-core = {path = "../gcore", features = ["async", "std"]} borrow_stack = {path = "../borrow_stack"} dyn-any = {path = "../../libraries/dyn-any", features = ["derive"]} graph-proc-macros = {path = "../proc-macro", optional = true} diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs new file mode 100644 index 00000000..17f80741 --- /dev/null +++ b/node-graph/gstd/src/any.rs @@ -0,0 +1,161 @@ +use dyn_any::{DynAny, StaticType}; +pub use graphene_core::{generic, ops /*, structural*/, Node, RefNode}; +use std::marker::PhantomData; + +fn fmt_error() -> String { + format!("DynAnyNode: input is not of correct type, expected {}", std::any::type_name::()) +} + +pub struct DynAnyNode<'n, N: RefNode, I>(pub N, pub PhantomData<&'n I>); +/*impl<'n, I: StaticType, N: RefNode<'n, &'n I, Output = O> + 'n, O: 'n + StaticType> Node<&'n dyn DynAny<'n>> for DynAnyNode<'n, N, I> { + type Output = Box + 'n>; + fn eval(self, input: &'n dyn DynAny<'n>) -> Self::Output { + let output = self.0.eval_ref(dyn_any::downcast_ref(input).expect(fmt_error::().as_str())); + Box::new(output) + } +}*/ +/* +impl<'n, I: StaticType, N: RefNode<&'n I, Output = O> + Copy + 'n, O: 'n + StaticType> Node<&'n dyn DynAny<'n>> for &'n DynAnyNode<'n, N, I> { + type Output = Box + 'n>; + fn eval(self, input: &'n dyn DynAny<'n>) -> Self::Output { + let output = self.0.eval_ref(dyn_any::downcast_ref(input).unwrap_or_else(|| panic!("{}", fmt_error::()))); + Box::new(output) + } +} +impl<'n, I: StaticType, N: RefNode<'n, I, Output = O> + 'n, O: 'n + StaticType> Node>> for DynAnyNode<'n, N, I> { + type Output = Box + 'n>; + fn eval(self, input: Box>) -> Self::Output { + let input: Box = dyn_any::downcast(input).unwrap_or_else(|| panic!("{}", fmt_error::())); + Box::new(self.0.eval_ref(*input)) + } +}*/ +impl<'n, I: StaticType, N: RefNode + Copy + 'n, O: 'n + StaticType> Node> for &'n DynAnyNode<'n, N, I> { + type Output = Any<'n>; + fn eval(self, input: Any<'n>) -> Self::Output { + let input: Box = dyn_any::downcast(input).unwrap_or_else(|| panic!("{}", fmt_error::())); + Box::new(self.0.eval_ref(*input)) + } +} + +impl<'n, I: StaticType, N: RefNode + 'n + Copy, O: 'n + StaticType> DynAnyNode<'n, N, I> +where + N::Output: StaticType, +{ + pub fn new(n: N) -> Self { + DynAnyNode(n, PhantomData) + } + pub fn into_erased(&'n self) -> impl RefNode, Output = Any<'n>> { + self + } + /*pub fn as_ref(&'n self) -> &'n AnyNode<'n> { + self + } + pub fn into_ref_box(self) -> Box + 'n)>, Output = Box<(dyn DynAny<'n> + 'n)>> + 'n> { + Box::new(self) + }*/ + pub fn into_ref(self: &'n &'n Self) -> &'n (dyn RefNode, Output = Any<'n>> + 'n) { + self + } +} + +/*impl<'n: 'static, I: StaticType, N, O: 'n + StaticType> DynAnyNode<'n, N, I> +where + N: RefNode + 'n + Copy, +{ + /*pub fn into_owned_erased(self) -> impl RefNode, Output = Any<'n>> + 'n { + self + }*/ + pub fn as_owned(&'n self) -> &'n (dyn RefNode, Output = Any<'n>> + 'n) { + self + } + /*pub fn into_owned_box(&self) -> Box> { + Box::new(self) + }*/ +}*/ +pub type Any<'n> = Box + 'n>; +pub type AnyNode<'n> = dyn RefNode, Output = Any<'n>>; + +pub trait DynNodeRef<'n>: RefNode<&'n dyn DynAny<'n>, Output = Box + 'n>> + 'n {} +impl<'n, N: RefNode<&'n dyn DynAny<'n>, Output = Box + 'n>> + 'n> DynNodeRef<'n> for N {} + +pub trait DynNodeOwned<'n>: RefNode, Output = Any<'n>> + 'n {} +impl<'n, N: RefNode, Output = Any<'n>> + 'n> DynNodeOwned<'n> for N {} + +/*impl<'n> Node>> for &'n Box> { + type Output = Box + 'n>; + fn eval(self, input: Box>) -> Self::Output { + (&*self as &dyn Node + 'n>, Output = Box + 'n>>).eval(input) + } +}*/ + +#[cfg(test)] +mod test { + use super::*; + use graphene_core::ops::{AddNode, IdNode}; + use graphene_core::value::ValueNode; + /*#[test] + pub fn dyn_input_compositon() { + use graphene_core::structural::After; + use graphene_core::structural::ComposeNode; + let id: DynAnyNode<_, u32> = DynAnyNode::new(IdNode); + let add: DynAnyNode<_, (u32, u32)> = DynAnyNode::new(AddNode); + let value: DynAnyNode<_, ()> = DynAnyNode::new(ValueNode((3u32, 4u32))); + let id = &id.as_owned(); + let add = add.as_owned(); + let value = value.as_owned(); + + /*let computation = ComposeNode::new(value, add); + let computation = id.after(add.after(value)); + let result: u32 = *dyn_any::downcast(computation.eval(&())).unwrap();*/ + }*/ + #[test] + #[should_panic] + pub fn dyn_input_invalid_eval_panic() { + static ADD: &DynAnyNode<&AddNode, (u32, u32)> = &DynAnyNode(&AddNode, PhantomData); + + let add = ADD.into_ref(); + add.eval_ref(Box::new(&("32", 32u32))); + } + /*#[test] + pub fn dyn_input_storage() { + let mut vec: Vec> = vec![]; + let id: DynAnyNode<_, u32> = DynAnyNode::new(IdNode); + let add: DynAnyNode<_, (u32, u32)> = DynAnyNode::new(AddNode); + let value: DynAnyNode<_, ()> = DynAnyNode::new(ValueNode((3u32, 4u32))); + + vec.push(add.into_ref_box()); + vec.push(id.into_ref_box()); + vec.push(value.into_ref_box()); + }*/ + #[test] + pub fn dyn_input_storage_composition() { + let mut vec: Vec<&(dyn RefNode)> = vec![]; + //let id: DynAnyNode<_, u32> = DynAnyNode::new(IdNode); + let value: &DynAnyNode<&ValueNode<(u32, u32)>, ()> = &DynAnyNode(&ValueNode((3u32, 4u32)), PhantomData); + let add: &DynAnyNode<&AddNode, &(u32, u32)> = &DynAnyNode(&AddNode, PhantomData); + + let value_ref = (&value).into_ref(); + let add_ref = (&add).into_ref(); + vec.push(value_ref); + vec.push(add_ref); + //vec.push(add.as_owned()); + //vec.push(id.as_owned()); + //let vec = vec.leak(); + + let n_value = vec[0]; + let n_add = vec[1]; + //let id = vec[2]; + + assert_eq!(*(dyn_any::downcast::<&(u32, u32)>(n_value.eval_ref(Box::new(()))).unwrap()), &(3u32, 4u32)); + fn compose<'n>( + first: &'n (dyn RefNode + 'n)>, Output = Box<(dyn DynAny<'n> + 'n)>> + 'n), + second: &'n (dyn RefNode + 'n)>, Output = Box<(dyn DynAny<'n> + 'n)>> + 'n), + input: Any<'n>, + ) -> Any<'n> { + second.eval_ref(first.eval_ref(input)) + } + let result = compose(n_value, n_add, Box::new(())); + assert_eq!(*dyn_any::downcast::(result).unwrap(), 7u32); + //let result: u32 = *dyn_any::downcast(computation.eval(Box::new(()))).unwrap(); + } +} diff --git a/node-graph/gstd/src/document.rs b/node-graph/gstd/src/document.rs new file mode 100644 index 00000000..309d2158 --- /dev/null +++ b/node-graph/gstd/src/document.rs @@ -0,0 +1,94 @@ +/* +use core::marker::PhantomData; + +use graphene_core::{structural::After, structural::ComposeNode, value::ValueNode, Node, RefNode}; + +use crate::any::Any; +use crate::any::DynAnyNode; + +pub trait DocumentNode: RefNode { + fn input_hints(&self) -> &'static [&'static str]; + fn input_types(&self) -> &'static [&'static str]; + fn inputs(&self) -> Vec { + self.input_hints().iter().zip(self.input_types()).map(|(a, b)| format!("{}{}", a, b)).collect() + } +} + +struct InjectNode + Copy, I>(N, PhantomData); + +impl<'n, N: Node + Copy, I> Node<&'n [&'n AnyNode<'n>]> for &'n InjectNode { + type Output = Box, Output = Any<'n>> + 'n>; + fn eval(self, input: &'n [&'n AnyNode<'n>]) -> Self::Output { + assert_eq!(input.len(), 1); + Box::new(ComposeNode::new(&DynAnyNode(input[0]), &DynAnyNode(self.0))) + } +} + +impl + Copy, I> InjectNode { + const TYPES: &'static [&'static str] = &[core::any::type_name::()]; + const HINTS: &'static [&'static str] = &["input: "]; +} +impl<'n, N: Node + Copy, I> DocumentNode<&'n [&'n AnyNode<'n>]> for &'n InjectNode { + fn input_hints(&self) -> &'static [&'static str] { + InjectNode::::HINTS + } + fn input_types(&self) -> &'static [&'static str] { + InjectNode::::TYPES + } +} + +pub type NodeId = u32; + +type AnyNode<'n> = dyn RefNode, Output = Any<'n>>; + +pub struct DocumentGraphNode<'n> { + pub id: NodeId, + pub inputs: Vec, + pub node: NodeWrapper<'n>, +} + +impl<'n> DocumentGraphNode<'n> { + pub fn new(id: NodeId, inputs: Vec, node: NodeWrapper<'n>) -> Self { + Self { id, inputs, node } + } +} + +pub struct NodeWrapper<'n> { + pub node: &'n (dyn Node, Output = Any<'n>> + 'n), + + pub path: &'static str, +} + +impl<'n> NodeWrapper<'n> { + pub fn new(node: &'n (dyn Node, Output = Any<'n>> + 'n), path: &'static str) -> Self { + Self { node, path } + } +} + +pub enum NodeInput { + Node(NodeId), + Default(ValueNode>), +} + +#[cfg(test)] +mod test { + use crate::any::DynAnyNode; + + use super::*; + use graphene_core::value::ValueNode; + + #[test] + fn inject_node() { + let inject_node = InjectNode(&ValueNode(4u32), PhantomData); + use super::DocumentNode; + /*assert_eq!( + (&inject_node as &dyn DocumentNode<&[&AnyNode], Output = ComposeNode<&AnyNode, ValueNode, ()>>).inputs(), + vec!["input: ()"] + );*/ + let any_inject = DynAnyNode(&inject_node, PhantomData); + let any_inject = Box::leak(Box::new(any_inject)); + let wrapped = NodeWrapper::new((&any_inject) as &(dyn Node<&[&AnyNode], Output = Any>), "grahpene_core::document::InjectNode"); + let document_node = DocumentGraphNode::new(0, vec![], wrapped); + } +} +*/ diff --git a/node-graph/gstd/src/lib.rs b/node-graph/gstd/src/lib.rs index 76ca35a9..ae9447b1 100644 --- a/node-graph/gstd/src/lib.rs +++ b/node-graph/gstd/src/lib.rs @@ -1,22 +1,17 @@ //pub mod value; -pub use graphene_core::{generic, ops /*, structural*/}; +//#![feature(const_type_name)] #[cfg(feature = "memoization")] pub mod memo; pub mod raster; +pub mod any; + +pub mod document; + pub use graphene_core::*; -/*use dyn_any::DynAny; -pub type DynNode<'n, T> = &'n (dyn Node<'n, Output = T> + 'n); -pub type DynAnyNode<'n> = &'n (dyn Node<'n, Output = &'n dyn DynAny<'n>> + 'n); -pub trait DynamicInput<'n> { - fn set_kwarg_by_name(&mut self, name: &str, value: DynAnyNode<'n>); - fn set_arg_by_index(&mut self, index: usize, value: DynAnyNode<'n>); -} - -*/ use quote::quote; use syn::{Expr, ExprPath, Type}; @@ -31,6 +26,7 @@ pub struct NodeGraph { pub output: Type, pub input: Type, } + pub enum NodeKind { Value(Expr), Input, diff --git a/node-graph/gstd/src/main.rs b/node-graph/gstd/src/main.rs index 1fdca03b..5eb05960 100644 --- a/node-graph/gstd/src/main.rs +++ b/node-graph/gstd/src/main.rs @@ -99,6 +99,7 @@ fn main() { ]; //println!("{}", node_graph(1)); + // let _nodegraph = NodeGraph { nodes, diff --git a/node-graph/gstd/src/memo.rs b/node-graph/gstd/src/memo.rs index 83121fcd..3c1a1e55 100644 --- a/node-graph/gstd/src/memo.rs +++ b/node-graph/gstd/src/memo.rs @@ -1,6 +1,5 @@ use graphene_core::{Cache, Node}; use once_cell::sync::OnceCell; -use std::marker::PhantomData; /// Caches the output of a given Node and acts as a proxy pub struct CacheNode, I> { diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 0e0676a0..ed4d6b31 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -2,13 +2,7 @@ use core::marker::PhantomData; use graphene_core::ops::FlatMapResultNode; use graphene_core::raster::color::Color; use graphene_core::structural::{ComposeNode, ConsNode}; -use graphene_core::{ - generic::FnNode, - ops::MapResultNode, - structural::{After, AfterRef}, - value::ValueNode, - Node, -}; +use graphene_core::{generic::FnNode, ops::MapResultNode, structural::After, value::ValueNode, Node}; use image::Pixel; use std::path::Path; diff --git a/node-graph/gstd/test-image-1-result.png b/node-graph/gstd/test-image-1-result.png new file mode 100644 index 0000000000000000000000000000000000000000..67da108e433085ed696fba7e24b3f3a9f44bf48a GIT binary patch literal 29388 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 literal 0 HcmV?d00001