Restructure GPU execution to model GPU pipelines in the node graph (#1088)

* Start implementing GpuExecutor for wgpu

* Implement read_output_buffer function

* Implement extraction node in the compiler

* Generate type annotations during shader compilation

* Start adding node wrapprs for graph execution api

* Wrap more of the api in nodes

* Restructure Pipeline to accept arbitrary shader inputs

* Adapt nodes to new trait definitions

* Start implementing gpu-compiler trait

* Adapt shader generation

* Hardstuck on pointer casts

* Pass nodes as references in gpu code to avoid zsts

* Update gcore to compile on the gpu

* Fix color doc tests

* Impl Node for node refs
This commit is contained in:
Dennis Kobert
2023-04-23 10:18:31 +02:00
committed by Keavon Chambers
parent 161bbc62b4
commit bdc1ef926a
43 changed files with 1874 additions and 515 deletions

View File

@@ -1,4 +1,7 @@
use crate::{raster::Sample, Color};
use bytemuck::{Pod, Zeroable};
use spirv_std::image::{Image2d, SampledImage};
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
@@ -6,3 +9,12 @@ pub struct PushConstants {
pub n: u32,
pub node: u32,
}
impl Sample for SampledImage<Image2d> {
type Pixel = Color;
fn sample(&self, pos: glam::DVec2) -> Option<Self::Pixel> {
let color = self.sample(pos);
Color::from_rgbaf32(color.x, color.y, color.z, color.w)
}
}

View File

@@ -18,6 +18,8 @@ pub mod value;
#[cfg(feature = "gpu")]
pub mod gpu;
pub mod storage;
pub mod raster;
#[cfg(feature = "alloc")]
pub mod transform;
@@ -44,8 +46,8 @@ pub use types::*;
pub trait NodeIO<'i, Input: 'i>: 'i + Node<'i, Input>
where
Self::Output: 'i + StaticType,
Input: 'i + StaticType,
Self::Output: 'i + StaticTypeSized,
Input: 'i + StaticTypeSized,
{
fn input_type(&self) -> TypeId {
TypeId::of::<Input::Static>()
@@ -54,7 +56,7 @@ where
core::any::type_name::<Input>()
}
fn output_type(&self) -> core::any::TypeId {
TypeId::of::<<Self::Output as StaticType>::Static>()
TypeId::of::<<Self::Output as StaticTypeSized>::Static>()
}
fn output_type_name(&self) -> &'static str {
core::any::type_name::<Self::Output>()
@@ -62,8 +64,8 @@ where
#[cfg(feature = "alloc")]
fn to_node_io(&self, parameters: Vec<Type>) -> NodeIOTypes {
NodeIOTypes {
input: concrete!(<Input as StaticType>::Static),
output: concrete!(<Self::Output as StaticType>::Static),
input: concrete!(<Input as StaticTypeSized>::Static),
output: concrete!(<Self::Output as StaticTypeSized>::Static),
parameters,
}
}
@@ -71,8 +73,8 @@ where
impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N
where
N::Output: 'i + StaticType,
I: 'i + StaticType,
N::Output: 'i + StaticTypeSized,
I: 'i + StaticTypeSized,
{
}
@@ -83,6 +85,13 @@ where
(**self).eval(input)
}
}*/
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for &'s N {
type Output = O;
fn eval(&'i self, input: I) -> Self::Output {
(**self).eval(input)
}
}
impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn for<'a> Node<'a, I, Output = O> {
type Output = O;
@@ -92,7 +101,7 @@ impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn for<'a> Node<'a, I, Output = O> {
}
use core::pin::Pin;
use dyn_any::StaticType;
use dyn_any::StaticTypeSized;
#[cfg(feature = "alloc")]
impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<Box<dyn for<'a> Node<'a, I, Output = O> + 'i>> {
type Output = O;

View File

@@ -1,5 +1,5 @@
use core::marker::PhantomData;
use core::ops::Add;
use core::ops::{Add, Mul};
use crate::Node;
@@ -30,6 +30,27 @@ where
first + second
}
pub struct MulParameterNode<Second> {
second: Second,
}
#[node_macro::node_fn(MulParameterNode)]
fn flat_map<U, T>(first: U, second: T) -> <U as Mul<T>>::Output
where
U: Mul<T>,
{
first * second
}
#[cfg(feature = "std")]
struct SizeOfNode {}
#[cfg(feature = "std")]
#[node_macro::node_fn(SizeOfNode)]
fn flat_map(ty: crate::Type) -> Option<usize> {
ty.size()
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SomeNode;
#[node_macro::node_fn(SomeNode)]

View File

@@ -2,6 +2,9 @@ use crate::raster::Color;
use crate::Node;
use dyn_any::{DynAny, StaticType};
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::Float;
#[derive(Clone, Debug, DynAny, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Quantization {

View File

@@ -4,50 +4,52 @@ use crate::Node;
use bytemuck::{Pod, Zeroable};
use glam::DVec2;
use num::Num;
#[cfg(not(target_arch = "spirv"))]
use num_traits::{cast::cast as num_cast, Num, NumCast};
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;
use spirv_std::num_traits::{cast::cast as num_cast, float::Float, FromPrimitive, Num, NumCast, ToPrimitive};
pub use self::color::Color;
pub mod adjustments;
#[cfg(not(target_arch = "spirv"))]
pub mod brightness_contrast;
pub mod color;
pub use adjustments::*;
pub trait Channel: Copy + Debug + num::Num + num::NumCast {
pub trait Channel: Copy + Debug + Num + NumCast {
fn to_linear<Out: Linear>(self) -> Out;
fn from_linear<In: Linear>(linear: In) -> Self;
fn to_f32(self) -> f32 {
num::cast(self).expect("Failed to convert channel to f32")
num_cast(self).expect("Failed to convert channel to f32")
}
fn from_f32(value: f32) -> Self {
num::cast(value).expect("Failed to convert f32 to channel")
num_cast(value).expect("Failed to convert f32 to channel")
}
fn to_f64(self) -> f64 {
num::cast(self).expect("Failed to convert channel to f64")
num_cast(self).expect("Failed to convert channel to f64")
}
fn from_f64(value: f64) -> Self {
num::cast(value).expect("Failed to convert f64 to channel")
num_cast(value).expect("Failed to convert f64 to channel")
}
fn to_channel<Out: Channel>(self) -> Out {
num::cast(self).expect("Failed to convert channel to channel")
num_cast(self).expect("Failed to convert channel to channel")
}
}
pub trait Linear: num::NumCast + Num {}
pub trait Linear: NumCast + Num {}
impl Linear for f32 {}
impl Linear for f64 {}
impl<T: Linear + Debug + Copy> Channel for T {
#[inline(always)]
fn to_linear<Out: Linear>(self) -> Out {
num::cast(self).expect("Failed to convert channel to linear")
num_cast(self).expect("Failed to convert channel to linear")
}
#[inline(always)]
fn from_linear<In: Linear>(linear: In) -> Self {
num::cast(linear).expect("Failed to convert linear to channel")
num_cast(linear).expect("Failed to convert linear to channel")
}
}
@@ -58,16 +60,16 @@ struct SRGBGammaFloat(f32);
impl Channel for SRGBGammaFloat {
#[inline(always)]
fn to_linear<Out: Linear>(self) -> Out {
let channel = num::cast::<_, f32>(self).expect("Failed to convert srgb to linear");
let channel = num_cast::<_, f32>(self).expect("Failed to convert srgb to linear");
let out = if channel <= 0.04045 { channel / 12.92 } else { ((channel + 0.055) / 1.055).powf(2.4) };
num::cast(out).expect("Failed to convert srgb to linear")
num_cast(out).expect("Failed to convert srgb to linear")
}
#[inline(always)]
fn from_linear<In: Linear>(linear: In) -> Self {
let linear = num::cast::<_, f32>(linear).expect("Failed to convert linear to srgb");
let linear = num_cast::<_, f32>(linear).expect("Failed to convert linear to srgb");
let out = if linear <= 0.0031308 { linear * 12.92 } else { 1.055 * linear.powf(1. / 2.4) - 0.055 };
num::cast(out).expect("Failed to convert linear to srgb")
num_cast(out).expect("Failed to convert linear to srgb")
}
}
pub trait RGBPrimaries {
@@ -98,6 +100,7 @@ impl<T> Serde for T {}
// TODO: Come up with a better name for this trait
pub trait Pixel: Clone + Pod + Zeroable {
#[cfg(not(target_arch = "spirv"))]
fn to_bytes(&self) -> Vec<u8> {
bytemuck::bytes_of(self).to_vec()
}
@@ -107,7 +110,7 @@ pub trait Pixel: Clone + Pod + Zeroable {
}
fn byte_size() -> usize {
std::mem::size_of::<Self>()
core::mem::size_of::<Self>()
}
}
pub trait RGB: Pixel {
@@ -448,6 +451,8 @@ pub struct ImageSlice<'a, Pixel> {
pub data: &'a [Pixel],
#[cfg(target_arch = "spirv")]
pub data: &'a (),
#[cfg(target_arch = "spirv")]
pub _marker: PhantomData<Pixel>,
}
unsafe impl<P: StaticTypeSized> StaticType for ImageSlice<'_, P> {
@@ -470,20 +475,17 @@ impl<'a, P> Default for ImageSlice<'a, P> {
width: Default::default(),
height: Default::default(),
data: &NOTHING,
_marker: PhantomData,
}
}
}
#[cfg(not(target_arch = "spirv"))]
impl<P: Copy + Debug + Pixel> Raster for ImageSlice<'_, P> {
type Pixel = P;
#[cfg(not(target_arch = "spirv"))]
fn get_pixel(&self, x: u32, y: u32) -> Option<P> {
self.data.get((x + y * self.width) as usize).copied()
}
#[cfg(target_arch = "spirv")]
fn get_pixel(&self, _x: u32, _y: u32) -> P {
Color::default()
}
fn width(&self) -> u32 {
self.width
}
@@ -605,6 +607,8 @@ mod image {
}
}
// TODO: Evaluate if this will be a problem for our use case.
/// Warning: This is an approximation of a hash, and is not guaranteed to not collide.
impl<P: Hash + Pixel> Hash for Image<P> {
fn hash<H: Hasher>(&self, state: &mut H) {
const HASH_SAMPLES: u64 = 1000;
@@ -661,7 +665,7 @@ mod image {
let Image { width, height, data } = self;
let to_gamma = |x| SRGBGammaFloat::from_linear(x);
let to_u8 = |x| (num::cast::<_, f32>(x).unwrap() * 255.) as u8;
let to_u8 = |x| (num_cast::<_, f32>(x).unwrap() * 255.) as u8;
let result_bytes = data
.into_iter()
@@ -670,7 +674,7 @@ mod image {
to_u8(to_gamma(color.r() / color.a().to_channel())),
to_u8(to_gamma(color.g() / color.a().to_channel())),
to_u8(to_gamma(color.b() / color.a().to_channel())),
(num::cast::<_, f32>(color.a()).unwrap() * 255.) as u8,
(num_cast::<_, f32>(color.a()).unwrap() * 255.) as u8,
]
})
.collect();

View File

@@ -3,6 +3,7 @@ use crate::Node;
use core::fmt::Debug;
use dyn_any::{DynAny, StaticType};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(target_arch = "spirv")]
@@ -457,8 +458,9 @@ fn vibrance_node(color: Color, vibrance: f64) -> Color {
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, DynAny, specta::Type)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
pub enum RedGreenBlue {
Red,
Green,
@@ -542,8 +544,9 @@ fn channel_mixer_node(
color.to_linear_srgb()
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, DynAny, specta::Type)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
pub enum RelativeAbsolute {
Relative,
Absolute,
@@ -559,7 +562,9 @@ impl core::fmt::Display for RelativeAbsolute {
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, DynAny, specta::Type)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
pub enum SelectiveColorChoice {
Reds,
Yellows,
@@ -797,17 +802,26 @@ fn exposure(color: Color, exposure: f64, offset: f64, gamma_correction: f64) ->
adjusted.map_rgb(|c: f32| c.clamp(0., 1.))
}
#[derive(Debug)]
pub struct IndexNode<Index> {
pub index: Index,
}
#[cfg(feature = "alloc")]
pub use index_node::IndexNode;
#[node_macro::node_fn(IndexNode)]
pub fn index_node(input: Vec<super::ImageFrame<Color>>, index: u32) -> super::ImageFrame<Color> {
if (index as usize) < input.len() {
input[index as usize].clone()
} else {
warn!("The number of segments is {} and the requested segment is {}!", input.len(), index);
super::ImageFrame::empty()
#[cfg(feature = "alloc")]
mod index_node {
use crate::raster::{Color, ImageFrame};
use crate::Node;
#[derive(Debug)]
pub struct IndexNode<Index> {
pub index: Index,
}
#[node_macro::node_fn(IndexNode)]
pub fn index_node(input: Vec<ImageFrame<Color>>, index: u32) -> ImageFrame<Color> {
if (index as usize) < input.len() {
input[index as usize].clone()
} else {
warn!("The number of segments is {} and the requested segment is {}!", input.len(), index);
ImageFrame::empty()
}
}
}

View File

@@ -30,7 +30,7 @@ fn brightness_contrast_legacy_node(_primary: (), brightness: f32, contrast: f32)
let brightness = brightness / 255.;
let contrast = contrast / 100.;
let contrast = if contrast > 0. { (contrast * std::f32::consts::FRAC_PI_2 - 0.01).tan() } else { contrast };
let contrast = if contrast > 0. { (contrast * core::f32::consts::FRAC_PI_2 - 0.01).tan() } else { contrast };
let combined = brightness * contrast + brightness - contrast / 2.;
@@ -172,7 +172,7 @@ fn solve_cubic_splines(cubic_spline_values: &CubicSplines) -> [f32; 4] {
// Eliminate the current column in all rows below the current one
for row_below_current in row + 1..4 {
assert!(augmented_matrix[row][row].abs() > std::f32::EPSILON);
assert!(augmented_matrix[row][row].abs() > core::f32::EPSILON);
let scale_factor = augmented_matrix[row_below_current][row] / augmented_matrix[row][row];
for col in row..5 {
@@ -184,7 +184,7 @@ fn solve_cubic_splines(cubic_spline_values: &CubicSplines) -> [f32; 4] {
// Gaussian elimination: back substitution
let mut solutions = [0.; 4];
for col in (0..4).rev() {
assert!(augmented_matrix[col][col].abs() > std::f32::EPSILON);
assert!(augmented_matrix[col][col].abs() > core::f32::EPSILON);
solutions[col] = augmented_matrix[col][4] / augmented_matrix[col][col];

View File

@@ -53,6 +53,7 @@ impl RGB for Color {
}
impl Pixel for Color {
#[cfg(not(target_arch = "spirv"))]
fn to_bytes(&self) -> Vec<u8> {
self.to_rgba8_srgb().to_vec()
}
@@ -121,7 +122,6 @@ impl Color {
/// let color = Color::from_rgbaf32(1.0, 1.0, 1.0, f32::NAN);
/// assert!(color == None);
/// ```
#[cfg(not(target_arch = "spirv"))]
pub fn from_rgbaf32(red: f32, green: f32, blue: f32, alpha: f32) -> Option<Color> {
if alpha > 1. || [red, green, blue, alpha].iter().any(|c| c.is_sign_negative() || !c.is_finite()) {
return None;
@@ -492,7 +492,7 @@ impl Color {
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// assert!(color.components() == (0.114, 0.103, 0.98, 0.97));
/// assert_eq!(color.components(), (0.114, 0.103, 0.98, 0.97));
/// ```
pub fn components(&self) -> (f32, f32, f32, f32) {
(self.red, self.green, self.blue, self.alpha)
@@ -585,7 +585,6 @@ impl Color {
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgba_str("7C67FA61").unwrap();
/// ```
#[cfg(not(target_arch = "spirv"))]
pub fn from_rgba_str(color_str: &str) -> Option<Color> {
if color_str.len() != 8 {
return None;
@@ -603,7 +602,6 @@ impl Color {
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgb_str("7C67FA").unwrap();
/// ```
#[cfg(not(target_arch = "spirv"))]
pub fn from_rgb_str(color_str: &str) -> Option<Color> {
if color_str.len() != 6 {
return None;

View File

@@ -0,0 +1,34 @@
use crate::Node;
use core::marker::PhantomData;
use core::ops::{DerefMut, Index, IndexMut};
struct SetNode<S, I, Storage, Index> {
storage: Storage,
index: Index,
_s: PhantomData<S>,
_i: PhantomData<I>,
}
#[node_macro::node_fn(SetNode<_S, _I>)]
fn set_node<T, _S, _I>(value: T, storage: &'any_input mut _S, index: _I)
where
_S: IndexMut<_I>,
_S::Output: DerefMut<Target = T> + Sized,
{
*storage.index_mut(index).deref_mut() = value;
}
struct GetNode<S, Storage> {
storage: Storage,
_s: PhantomData<S>,
}
#[node_macro::node_fn(GetNode<_S>)]
fn get_node<_S, I>(index: I, storage: &'any_input _S) -> &'input _S::Output
where
_S: Index<I>,
_S::Output: Sized,
{
storage.index(index)
}

View File

@@ -2,60 +2,47 @@ use core::marker::PhantomData;
use crate::Node;
pub struct ComposeNode<First: for<'i> Node<'i, I>, Second: for<'i> Node<'i, <First as Node<'i, I>>::Output>, I> {
#[derive(Clone)]
pub struct ComposeNode<First, Second, I> {
first: First,
second: Second,
phantom: PhantomData<I>,
}
impl<'i, Input: 'i, First, Second> Node<'i, Input> for ComposeNode<First, Second, Input>
impl<'i, 'f: 'i, 's: 'i, Input: 'i, First, Second> Node<'i, Input> for ComposeNode<First, Second, Input>
where
First: for<'a> Node<'a, Input> + 'i,
Second: for<'a> Node<'a, <First as Node<'a, Input>>::Output> + 'i,
First: Node<'i, Input>,
Second: Node<'i, <First as Node<'i, Input>>::Output> + 'i,
{
type Output = <Second as Node<'i, <First as Node<'i, Input>>::Output>>::Output;
fn eval(&'i self, input: Input) -> Self::Output {
let arg = self.first.eval(input);
self.second.eval(arg)
let second = &self.second;
second.eval(arg)
}
}
impl<First, Second, Input> ComposeNode<First, Second, Input>
impl<'i, First, Second, Input: 'i> ComposeNode<First, Second, Input>
where
First: for<'a> Node<'a, Input>,
Second: for<'a> Node<'a, <First as Node<'a, Input>>::Output>,
First: Node<'i, Input>,
Second: Node<'i, <First as Node<'i, Input>>::Output>,
{
pub const fn new(first: First, second: Second) -> Self {
ComposeNode::<First, Second, Input> { first, second, phantom: PhantomData }
}
}
// impl Clone for ComposeNode<First, Second, Input>
impl<First, Second, Input> Clone for ComposeNode<First, Second, Input>
where
First: for<'a> Node<'a, Input> + Clone,
Second: for<'a> Node<'a, <First as Node<'a, Input>>::Output> + Clone,
{
fn clone(&self) -> Self {
ComposeNode::<First, Second, Input> {
first: self.first.clone(),
second: self.second.clone(),
phantom: PhantomData,
}
}
}
pub trait Then<'i, Input: 'i>: Sized {
fn then<Second>(self, second: Second) -> ComposeNode<Self, Second, Input>
where
Self: for<'a> Node<'a, Input>,
Second: for<'a> Node<'a, <Self as Node<'a, Input>>::Output>,
Self: Node<'i, Input>,
Second: Node<'i, <Self as Node<'i, Input>>::Output>,
{
ComposeNode::new(self, second)
}
}
impl<'i, First: for<'a> Node<'a, Input>, Input: 'i> Then<'i, Input> for First {}
impl<'i, First: Node<'i, Input>, Input: 'i> Then<'i, Input> for First {}
pub struct ConsNode<I: From<()>, Root>(pub Root, PhantomData<I>);
@@ -89,4 +76,16 @@ mod test {
let type_erased = &compose as &dyn for<'i> Node<'i, (), Output = &'i u32>;
assert_eq!(type_erased.eval(()), &4u32);
}
#[test]
fn test_ref_eval() {
let value = ValueNode::new(5);
assert_eq!((&value).eval(()), &5);
let id = IdNode::new();
let compose = ComposeNode::new(&value, &id);
assert_eq!(compose.eval(()), &5);
}
}

View File

@@ -2,6 +2,7 @@ use core::any::TypeId;
#[cfg(not(feature = "std"))]
pub use alloc::borrow::Cow;
use dyn_any::StaticType;
#[cfg(feature = "std")]
pub use std::borrow::Cow;
@@ -28,6 +29,8 @@ macro_rules! concrete {
Type::Concrete(TypeDescriptor {
id: Some(core::any::TypeId::of::<$type>()),
name: Cow::Borrowed(core::any::type_name::<$type>()),
size: core::mem::size_of::<$type>(),
align: core::mem::align_of::<$type>(),
})
};
}
@@ -65,6 +68,8 @@ pub struct TypeDescriptor {
#[specta(skip)]
pub id: Option<TypeId>,
pub name: Cow<'static, str>,
pub size: usize,
pub align: usize,
}
impl core::hash::Hash for TypeDescriptor {
@@ -137,6 +142,32 @@ impl Type {
}
}
impl Type {
pub fn new<T: StaticType + Sized>() -> Self {
Self::Concrete(TypeDescriptor {
id: Some(TypeId::of::<T::Static>()),
name: Cow::Borrowed(core::any::type_name::<T::Static>()),
size: core::mem::size_of::<T>(),
align: core::mem::align_of::<T>(),
})
}
pub fn size(&self) -> Option<usize> {
match self {
Self::Generic(_) => None,
Self::Concrete(ty) => Some(ty.size),
Self::Fn(_, _) => None,
}
}
pub fn align(&self) -> Option<usize> {
match self {
Self::Generic(_) => None,
Self::Concrete(ty) => Some(ty.align),
Self::Fn(_, _) => None,
}
}
}
impl core::fmt::Debug for Type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {

View File

@@ -40,7 +40,7 @@ impl<T: Clone> Clone for ValueNode<T> {
}
impl<T: Clone + Copy> Copy for ValueNode<T> {}
#[derive(Clone)]
#[derive(Clone, Copy)]
pub struct ClonedNode<T: Clone>(pub T);
impl<'i, T: Clone + 'i> Node<'i, ()> for ClonedNode<T> {
@@ -61,7 +61,22 @@ impl<T: Clone> From<T> for ClonedNode<T> {
ClonedNode::new(value)
}
}
impl<T: Clone + Copy> Copy for ClonedNode<T> {}
#[derive(Clone, Copy)]
pub struct CopiedNode<T: Copy>(pub T);
impl<'i, T: Copy + 'i> Node<'i, ()> for CopiedNode<T> {
type Output = T;
fn eval(&'i self, _input: ()) -> Self::Output {
self.0
}
}
impl<T: Copy> CopiedNode<T> {
pub const fn new(value: T) -> CopiedNode<T> {
CopiedNode(value)
}
}
#[derive(Default)]
pub struct DefaultNode<T>(PhantomData<T>);