diff --git a/Cargo.lock b/Cargo.lock index 7732bc2b71..e1eb3ede48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1646,6 +1646,7 @@ dependencies = [ "kurbo", "log", "node-macro", + "once_cell", "serde", "specta", "spirv-std", @@ -2171,6 +2172,7 @@ dependencies = [ "graphene-std", "log", "num-traits", + "once_cell", "rand_chacha 0.3.1", "serde", ] diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 0bd572d099..9fbda00202 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -10,12 +10,13 @@ use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer; use document_legacy::LayerId; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, NodeOutput}; +use graphene_core::*; mod document_node_types; mod node_properties; use glam::IVec2; -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum FrontendGraphDataType { #[default] #[serde(rename = "general")] @@ -310,7 +311,7 @@ impl NodeGraphMessageHandler { exposed_inputs: node .inputs .iter() - .zip(node_type.inputs) + .zip(node_type.inputs.iter()) .skip(1) .filter(|(input, _)| input.is_exposed()) .map(|(_, input_type)| NodeGraphInput { @@ -769,7 +770,7 @@ impl MessageHandler= node.inputs.len() { - node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network)); + node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network(generic!(T)))); } node.inputs[input_index] = NodeInput::Value { tagged_value: value, exposed: false }; if network.connected_to_output(*node_id) { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 9b72a2ed63..f68f5a3941 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -1,16 +1,19 @@ use super::{node_properties, FrontendGraphDataType, FrontendNodeType}; use crate::messages::layout::utility_types::layout_widget::LayoutGroup; use crate::node_graph_executor::NodeGraphExecutor; +use once_cell::sync::Lazy; use graph_craft::document::value::*; use graph_craft::document::*; use graph_craft::imaginate_input::ImaginateSamplingMethod; -use graph_craft::proto::{NodeIdentifier, Type}; -use graph_craft::{concrete, generic}; + +use graph_craft::NodeIdentifier; use graphene_core::raster::{Color, Image, LuminanceCalculation}; +use graphene_core::*; use std::collections::VecDeque; +#[derive(Debug, Clone, PartialEq, Hash)] pub struct DocumentInputType { pub name: &'static str, pub data_type: FrontendGraphDataType, @@ -18,7 +21,11 @@ pub struct DocumentInputType { } impl DocumentInputType { - pub const fn new(name: &'static str, tagged_value: TaggedValue, exposed: bool) -> Self { + pub fn new(name: &'static str, data_type: FrontendGraphDataType, default: NodeInput) -> Self { + Self { name, data_type, default } + } + + pub fn value(name: &'static str, tagged_value: TaggedValue, exposed: bool) -> Self { let data_type = FrontendGraphDataType::with_tagged_value(&tagged_value); let default = NodeInput::value(tagged_value, exposed); Self { name, data_type, default } @@ -33,6 +40,7 @@ impl DocumentInputType { } } +#[derive(Debug, Clone, PartialEq, Hash)] pub struct DocumentOutputType { pub name: &'static str, pub data_type: FrontendGraphDataType, @@ -61,8 +69,8 @@ pub enum NodeImplementation { } impl NodeImplementation { - pub const fn proto(name: &'static str, types: &'static [Type]) -> Self { - Self::ProtoNode(NodeIdentifier::new(name, types)) + pub fn proto(name: &'static str) -> Self { + Self::ProtoNode(NodeIdentifier::new(name)) } } @@ -71,486 +79,458 @@ pub struct DocumentNodeType { pub name: &'static str, pub category: &'static str, pub identifier: NodeImplementation, - pub inputs: &'static [DocumentInputType], - pub outputs: &'static [DocumentOutputType], + pub inputs: Vec, + pub outputs: Vec, pub properties: fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec, } -fn document_node_types() -> Vec { - let mut vec: Vec<_> = STATIC_NODES.to_vec(); - - const GAUSSIAN_BLUR_NODE_INPUTS: &[DocumentInputType] = &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Radius", TaggedValue::U32(3), false), - DocumentInputType::new("Sigma", TaggedValue::F64(1.), false), - ]; - let blur = DocumentNodeType { - name: "Gaussian Blur", - category: "Image Filters", - identifier: NodeImplementation::DocumentNode(NodeNetwork { - inputs: vec![0, 1, 1], - outputs: vec![NodeOutput::new(1, 0)], - nodes: vec![ - ( - 0, - DocumentNode { - name: "CacheNode".to_string(), - inputs: vec![NodeInput::Network], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode", &[concrete!("Image")])), - metadata: Default::default(), - }, - ), - ( - 1, - DocumentNode { - name: "BlurNode".to_string(), - inputs: vec![NodeInput::node(0, 0), NodeInput::Network, NodeInput::Network, NodeInput::node(0, 0)], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::BlurNode", &[concrete!("Image")])), - metadata: Default::default(), - }, - ), - ] - .into_iter() - .collect(), - ..Default::default() - }), - inputs: GAUSSIAN_BLUR_NODE_INPUTS, - outputs: &[DocumentOutputType { - name: "Image", - data_type: FrontendGraphDataType::Raster, - }], - properties: node_properties::blur_image_properties, - }; - vec.push(blur); - - const INPUT_NODE_INPUTS: &[DocumentInputType] = &[ - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::Network, - }, - DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false), - ]; - let input = DocumentNodeType { - name: "Input", - category: "Ignore", - identifier: NodeImplementation::DocumentNode(NodeNetwork { - inputs: vec![0, 1], - outputs: vec![NodeOutput::new(0, 0), NodeOutput::new(1, 0)], - nodes: [ - DocumentNode { - name: "Identity".to_string(), - inputs: vec![NodeInput::Network], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), - metadata: Default::default(), - }, - DocumentNode { - name: "Identity".to_string(), - inputs: vec![NodeInput::Network], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), - metadata: Default::default(), - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (id as NodeId, node)) - .collect(), - ..Default::default() - }), - inputs: INPUT_NODE_INPUTS, - outputs: &[ - DocumentOutputType { - name: "Image", - data_type: FrontendGraphDataType::Raster, - }, - DocumentOutputType { - name: "Transform", - data_type: FrontendGraphDataType::Number, - }, - ], - properties: node_properties::input_properties, - }; - vec.push(input); - - vec -} - // We use the once cell for lazy initialization to avoid the overhead of reconstructing the node list every time. // TODO: make document nodes not require a `'static` lifetime to avoid having to split the construction into const and non-const parts. -static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(document_node_types); +static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(static_nodes); // TODO: Dynamic node library -static STATIC_NODES: &[DocumentNodeType] = &[ - DocumentNodeType { - name: "Identity", - category: "General", - identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]), - inputs: &[DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::node(0, 0), - }], - outputs: &[DocumentOutputType::new("Out", FrontendGraphDataType::General)], - properties: |_document_node, _node_id, _context| node_properties::string_properties("The identity node simply returns the input"), - }, - DocumentNodeType { - name: "Image", - category: "Ignore", - identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]), - inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), false)], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"), - }, - // DocumentNodeType { - // name: "Input", - // category: "Ignore", - // identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]), - // inputs: &[DocumentInputType { - // name: "In", - // data_type: FrontendGraphDataType::Raster, - // default: NodeInput::Network, - // }], - // outputs: &[DocumentOutputType::new("Out", FrontendGraphDataType::Raster)], - // properties: node_properties::input_properties, - // }, - DocumentNodeType { - name: "Output", - category: "Ignore", - identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]), - inputs: &[DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::Image(Image::empty()), true), - }], - outputs: &[], - properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame"), - }, - DocumentNodeType { - name: "Image Frame", - category: "General", - identifier: NodeImplementation::proto("graphene_std::raster::ImageFrameNode<_>", &[concrete!("Image"), concrete!("DAffine2")]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: |_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"), - }, - DocumentNodeType { - name: "Grayscale", - category: "Image Adjustments", - identifier: NodeImplementation::proto( - "graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>", - &[ - concrete!("Image"), - concrete!("Color"), - concrete!("f64"), - concrete!("f64"), - concrete!("f64"), - concrete!("f64"), - concrete!("f64"), - concrete!("f64"), +fn static_nodes() -> Vec { + vec![ + DocumentNodeType { + name: "Identity", + category: "General", + identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), + inputs: vec![DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::General, + default: NodeInput::node(0, 0), + }], + outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)], + properties: |_document_node, _node_id, _context| node_properties::string_properties("The identity node simply returns the input"), + }, + DocumentNodeType { + name: "Image", + category: "Ignore", + identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), + inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), false)], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"), + }, + // DocumentNodeType { + // name: "Input", + // category: "Ignore", + // identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), + // inputs: vec![DocumentInputType { + // name: "In", + // data_type: FrontendGraphDataType::Raster, + // default: NodeInput::Network, + // }], + // outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Raster)], + // properties: node_properties::input_properties, + // }, + DocumentNodeType { + name: "Input", + category: "Ignore", + identifier: NodeImplementation::DocumentNode(NodeNetwork { + inputs: vec![0, 1], + outputs: vec![NodeOutput::new(0, 0), NodeOutput::new(1, 0)], + nodes: [ + DocumentNode { + name: "Identity".to_string(), + inputs: vec![NodeInput::Network(concrete!(Image))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), + metadata: Default::default(), + }, + DocumentNode { + name: "Identity".to_string(), + inputs: vec![NodeInput::Network(concrete!(DAffine2))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), + metadata: Default::default(), + }, + ] + .into_iter() + .enumerate() + .map(|(id, node)| (id as NodeId, node)) + .collect(), + ..Default::default() + }), + inputs: vec![ + DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::General, + default: NodeInput::Network(concrete!(Image)), + }, + DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false), ], - ), - inputs: &[ - DocumentInputType { - name: "Image", + outputs: vec![ + DocumentOutputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + }, + DocumentOutputType { + name: "Transform", + data_type: FrontendGraphDataType::Number, + }, + ], + properties: node_properties::input_properties, + }, + DocumentNodeType { + name: "Output", + category: "Ignore", + identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), + inputs: vec![DocumentInputType { + name: "In", data_type: FrontendGraphDataType::Raster, default: NodeInput::value(TaggedValue::Image(Image::empty()), true), - }, - DocumentInputType { - name: "Tint", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::Color(Color::BLACK), false), - }, - DocumentInputType { - name: "Reds", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::F64(50.), false), - }, - DocumentInputType { - name: "Yellows", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::F64(50.), false), - }, - DocumentInputType { - name: "Greens", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::F64(50.), false), - }, - DocumentInputType { - name: "Cyans", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::F64(50.), false), - }, - DocumentInputType { - name: "Blues", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::F64(50.), false), - }, - DocumentInputType { - name: "Magentas", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::F64(50.), false), - }, - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::grayscale_properties, - }, - DocumentNodeType { - name: "Luminance", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_core::raster::LuminanceNode<_>", &[concrete!("Image"), concrete!("LuminanceCalculation")]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::luminance_properties, - }, - #[cfg(feature = "gpu")] - DocumentNodeType { - name: "GpuImage", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode", &[concrete!("Image")]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType { - name: "Path", - data_type: FrontendGraphDataType::Text, - default: NodeInput::value(TaggedValue::String(String::new()), false), - }, - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::gpu_map_properties, - }, - #[cfg(feature = "quantization")] - DocumentNodeType { - name: "QuantizeImage", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_std::quantization::GenerateQuantizationNode", &[concrete!("Image")]), - inputs: &[ - DocumentInputType { + }], + outputs: vec![], + properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame"), + }, + DocumentNodeType { + name: "Image Frame", + category: "General", + identifier: NodeImplementation::proto("graphene_std::raster::ImageFrameNode<_>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: |_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"), + }, + DocumentNodeType { + name: "Grayscale", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>"), + inputs: vec![ + DocumentInputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + }, + DocumentInputType { + name: "Tint", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::Color(Color::BLACK), false), + }, + DocumentInputType { + name: "Reds", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(50.), false), + }, + DocumentInputType { + name: "Yellows", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(50.), false), + }, + DocumentInputType { + name: "Greens", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(50.), false), + }, + DocumentInputType { + name: "Cyans", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(50.), false), + }, + DocumentInputType { + name: "Blues", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(50.), false), + }, + DocumentInputType { + name: "Magentas", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(50.), false), + }, + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::grayscale_properties, + }, + DocumentNodeType { + name: "Luminance", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::LuminanceNode<_>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::luminance_properties, + }, + DocumentNodeType { + name: "Gaussian Blur", + category: "Image Filters", + identifier: NodeImplementation::DocumentNode(NodeNetwork { + inputs: vec![0, 1, 1], + outputs: vec![NodeOutput::new(1, 0)], + nodes: vec![ + ( + 0, + DocumentNode { + name: "CacheNode".to_string(), + inputs: vec![NodeInput::Network(concrete!(Image))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")), + metadata: Default::default(), + }, + ), + ( + 1, + DocumentNode { + name: "BlurNode".to_string(), + inputs: vec![NodeInput::node(0, 0), NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(f64)), NodeInput::node(0, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::BlurNode")), + metadata: Default::default(), + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Radius", TaggedValue::U32(3), false), + DocumentInputType::value("Sigma", TaggedValue::F64(1.), false), + ], + outputs: vec![DocumentOutputType { name: "Image", data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::Image(Image::empty()), true), - }, - DocumentInputType { - name: "samples", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::U32(100), false), - }, - DocumentInputType { - name: "Fn index", - data_type: FrontendGraphDataType::Number, - default: NodeInput::value(TaggedValue::U32(0), false), - }, - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::quantize_properties, - }, - DocumentNodeType { - name: "Cache", - category: "Structural", - identifier: NodeImplementation::proto("graphene_std::memo::CacheNode", &[concrete!("Image")]), - inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::no_properties, - }, - DocumentNodeType { - name: "Invert RGB", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_core::raster::InvertRGBNode", &[concrete!("Image")]), - inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::no_properties, - }, - DocumentNodeType { - name: "Hue/Saturation", - category: "Image Adjustments", - identifier: NodeImplementation::proto( - "graphene_core::raster::HueSaturationNode<_, _, _>", - &[concrete!("Image"), concrete!("f64"), concrete!("f64"), concrete!("f64")], - ), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Hue Shift", TaggedValue::F64(0.), false), - DocumentInputType::new("Saturation Shift", TaggedValue::F64(0.), false), - DocumentInputType::new("Lightness Shift", TaggedValue::F64(0.), false), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::adjust_hsl_properties, - }, - DocumentNodeType { - name: "Brightness/Contrast", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_core::raster::BrightnessContrastNode<_, _>", &[concrete!("Image"), concrete!("f64"), concrete!("f64")]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Brightness", TaggedValue::F64(0.), false), - DocumentInputType::new("Contrast", TaggedValue::F64(0.), false), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::brighten_image_properties, - }, - DocumentNodeType { - name: "Threshold", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_core::raster::ThresholdNode<_, _>", &[concrete!("Image"), concrete!("LuminanceCalculation"), concrete!("f64")]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false), - DocumentInputType::new("Threshold", TaggedValue::F64(50.), false), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::adjust_threshold_properties, - }, - DocumentNodeType { - name: "Vibrance", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_core::raster::VibranceNode<_>", &[concrete!("Image"), concrete!("f64")]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Vibrance", TaggedValue::F64(0.), false), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::adjust_vibrance_properties, - }, - DocumentNodeType { - name: "Opacity", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_core::raster::OpacityNode<_>", &[concrete!("Image"), concrete!("f64")]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Factor", TaggedValue::F64(100.), false), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::multiply_opacity, - }, - DocumentNodeType { - name: "Posterize", - category: "Image Adjustments", - identifier: NodeImplementation::proto("graphene_core::raster::PosterizeNode<_>", &[concrete!("Image"), concrete!("f64")]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Value", TaggedValue::F64(4.), false), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::posterize_properties, - }, - DocumentNodeType { - name: "Exposure", - category: "Image Adjustments", - identifier: NodeImplementation::proto( - "graphene_core::raster::ExposureNode<_, _, _>", - &[concrete!("Image"), concrete!("f64"), concrete!("f64"), concrete!("f64")], - ), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Exposure", TaggedValue::F64(0.), false), - DocumentInputType::new("Offset", TaggedValue::F64(0.), false), - DocumentInputType::new("Gamma Correction", TaggedValue::F64(1.), false), - ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], - properties: node_properties::exposure_properties, - }, - IMAGINATE_NODE, - DocumentNodeType { - name: "Add", - category: "Math", - identifier: NodeImplementation::proto("graphene_core::ops::AddParameterNode<_>", &[concrete!("f64"), concrete!("f64")]), - inputs: &[ - DocumentInputType::new("Input", TaggedValue::F64(0.), true), - DocumentInputType::new("Addend", TaggedValue::F64(0.), true), - ], - outputs: &[DocumentOutputType::new("Output", FrontendGraphDataType::Number)], - properties: node_properties::add_properties, - }, - /*DocumentNodeType { - name: "Unit Circle Generator", - category: "Vector", - identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]), - inputs: &[DocumentInputType::none()], - outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], - properties: node_properties::no_properties, - }, - DocumentNodeType { - name: "Unit Square Generator", - category: "Vector", - identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]), - inputs: &[DocumentInputType::none()], - outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], - properties: node_properties::no_properties, - }, - DocumentNodeType { - name: "Path Generator", - category: "Vector", - identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]), - inputs: &[DocumentInputType { - name: "Path Data", - data_type: FrontendGraphDataType::Subpath, - default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false), - }], - outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], - properties: node_properties::no_properties, - }, - DocumentNodeType { - name: "Transform Subpath", - category: "Vector", - identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]), - inputs: &[ - DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true), - DocumentInputType::new("Translation", TaggedValue::DVec2(DVec2::ZERO), false), - DocumentInputType::new("Rotation", TaggedValue::F64(0.), false), - DocumentInputType::new("Scale", TaggedValue::DVec2(DVec2::ONE), false), - DocumentInputType::new("Skew", TaggedValue::DVec2(DVec2::ZERO), false), - ], - outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], - properties: node_properties::transform_properties, - }, - DocumentNodeType { - name: "Blit Subpath", - category: "Vector", - identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::BlitSubpath", &[]), - inputs: &[ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true), - ], - outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)], - properties: node_properties::no_properties, - },*/ -]; + }], + properties: node_properties::blur_image_properties, + }, + #[cfg(feature = "gpu")] + DocumentNodeType { + name: "GpuImage", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"), + inputs: vec![ + DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType { + name: "Path", + data_type: FrontendGraphDataType::Text, + default: NodeInput::value(TaggedValue::String(String::new()), false), + }, + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::gpu_map_properties, + }, + #[cfg(feature = "quantization")] + DocumentNodeType { + name: "QuantizeImage", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_std::quantization::GenerateQuantizationNode"), + inputs: vec![ + DocumentInputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + }, + DocumentInputType { + name: "samples", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::U32(100), false), + }, + DocumentInputType { + name: "Fn index", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::U32(0), false), + }, + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::quantize_properties, + }, + DocumentNodeType { + name: "Cache", + category: "Structural", + identifier: NodeImplementation::proto("graphene_std::memo::CacheNode"), + inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true)], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Invert RGB", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::InvertRGBNode"), + inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true)], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Hue/Saturation", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::HueSaturationNode<_, _, _>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Hue Shift", TaggedValue::F64(0.), false), + DocumentInputType::value("Saturation Shift", TaggedValue::F64(0.), false), + DocumentInputType::value("Lightness Shift", TaggedValue::F64(0.), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::adjust_hsl_properties, + }, + DocumentNodeType { + name: "Brightness/Contrast", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::BrightnessContrastNode<_, _>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Brightness", TaggedValue::F64(0.), false), + DocumentInputType::value("Contrast", TaggedValue::F64(0.), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::brighten_image_properties, + }, + DocumentNodeType { + name: "Threshold", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::ThresholdNode<_, _>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false), + DocumentInputType::value("Threshold", TaggedValue::F64(50.), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::adjust_threshold_properties, + }, + DocumentNodeType { + name: "Vibrance", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::VibranceNode<_>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Vibrance", TaggedValue::F64(0.), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::adjust_vibrance_properties, + }, + DocumentNodeType { + name: "Opacity", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::OpacityNode<_>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Factor", TaggedValue::F64(100.), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::multiply_opacity, + }, + DocumentNodeType { + name: "Posterize", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::PosterizeNode<_>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Value", TaggedValue::F64(4.), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::posterize_properties, + }, + DocumentNodeType { + name: "Exposure", + category: "Image Adjustments", + identifier: NodeImplementation::proto("graphene_core::raster::ExposureNode<_, _, _>"), + inputs: vec![ + DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Exposure", TaggedValue::F64(0.), false), + DocumentInputType::value("Offset", TaggedValue::F64(0.), false), + DocumentInputType::value("Gamma Correction", TaggedValue::F64(1.), false), + ], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + properties: node_properties::exposure_properties, + }, + DocumentNodeType { + name: "Add", + category: "Math", + identifier: NodeImplementation::proto("graphene_core::ops::AddParameterNode<_>"), + inputs: vec![ + DocumentInputType::value("Input", TaggedValue::F64(0.), true), + DocumentInputType::value("Addend", TaggedValue::F64(0.), true), + ], + outputs: vec![DocumentOutputType::new("Output", FrontendGraphDataType::Number)], + properties: node_properties::add_properties, + }, + (*IMAGINATE_NODE).clone(), + /*DocumentNodeType { + name: "Unit Circle Generator", + category: "Vector", + identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]), + inputs: vec![DocumentInputType::none()], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Unit Square Generator", + category: "Vector", + identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]), + inputs: vec![DocumentInputType::none()], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Path Generator", + category: "Vector", + identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), + inputs: vec![DocumentInputType { + name: "Path Data", + data_type: FrontendGraphDataType::Subpath, + default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false), + }], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Transform Subpath", + category: "Vector", + identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]), + inputs: vec![ + DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true), + DocumentInputType::new("Translation", TaggedValue::DVec2(DVec2::ZERO), false), + DocumentInputType::new("Rotation", TaggedValue::F64(0.), false), + DocumentInputType::new("Scale", TaggedValue::DVec2(DVec2::ONE), false), + DocumentInputType::new("Skew", TaggedValue::DVec2(DVec2::ZERO), false), + ], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::transform_properties, + }, + DocumentNodeType { + name: "Blit Subpath", + category: "Vector", + identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::BlitSubpath", &[]), + inputs: vec![ + DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true), + ], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)], + properties: node_properties::no_properties, + },*/ + ] +} -pub const IMAGINATE_NODE: DocumentNodeType = DocumentNodeType { +pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentNodeType { name: "Imaginate", category: "Image Synthesis", - identifier: NodeImplementation::proto("graphene_std::raster::ImaginateNode<_>", &[concrete!("Image"), concrete!("Option>")]), - inputs: &[ - DocumentInputType::new("Input Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true), - DocumentInputType::new("Seed", TaggedValue::F64(0.), false), // Remember to keep index used in `NodeGraphFrameImaginateRandom` updated with this entry's index - DocumentInputType::new("Resolution", TaggedValue::OptionalDVec2(None), false), - DocumentInputType::new("Samples", TaggedValue::F64(30.), false), - DocumentInputType::new("Sampling Method", TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false), - DocumentInputType::new("Prompt Guidance", TaggedValue::F64(7.5), false), - DocumentInputType::new("Prompt", TaggedValue::String(String::new()), false), - DocumentInputType::new("Negative Prompt", TaggedValue::String(String::new()), false), - DocumentInputType::new("Adapt Input Image", TaggedValue::Bool(false), false), - DocumentInputType::new("Image Creativity", TaggedValue::F64(66.), false), - DocumentInputType::new("Masking Layer", TaggedValue::LayerPath(None), false), - DocumentInputType::new("Inpaint", TaggedValue::Bool(true), false), - DocumentInputType::new("Mask Blur", TaggedValue::F64(4.), false), - DocumentInputType::new("Mask Starting Fill", TaggedValue::ImaginateMaskStartingFill(ImaginateMaskStartingFill::Fill), false), - DocumentInputType::new("Improve Faces", TaggedValue::Bool(false), false), - DocumentInputType::new("Tiling", TaggedValue::Bool(false), false), + identifier: NodeImplementation::proto("graphene_std::raster::ImaginateNode<_>"), + inputs: vec![ + DocumentInputType::value("Input Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true), + DocumentInputType::value("Seed", TaggedValue::F64(0.), false), // Remember to keep index used in `NodeGraphFrameImaginateRandom` updated with this entry's index + DocumentInputType::value("Resolution", TaggedValue::OptionalDVec2(None), false), + DocumentInputType::value("Samples", TaggedValue::F64(30.), false), + DocumentInputType::value("Sampling Method", TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false), + DocumentInputType::value("Prompt Guidance", TaggedValue::F64(7.5), false), + DocumentInputType::value("Prompt", TaggedValue::String(String::new()), false), + DocumentInputType::value("Negative Prompt", TaggedValue::String(String::new()), false), + DocumentInputType::value("Adapt Input Image", TaggedValue::Bool(false), false), + DocumentInputType::value("Image Creativity", TaggedValue::F64(66.), false), + DocumentInputType::value("Masking Layer", TaggedValue::LayerPath(None), false), + DocumentInputType::value("Inpaint", TaggedValue::Bool(true), false), + DocumentInputType::value("Mask Blur", TaggedValue::F64(4.), false), + DocumentInputType::value("Mask Starting Fill", TaggedValue::ImaginateMaskStartingFill(ImaginateMaskStartingFill::Fill), false), + DocumentInputType::value("Improve Faces", TaggedValue::Bool(false), false), + DocumentInputType::value("Tiling", TaggedValue::Bool(false), false), // Non-user status (is document input the right way to do this?) - DocumentInputType::new("Cached Data", TaggedValue::RcImage(None), false), - DocumentInputType::new("Percent Complete", TaggedValue::F64(0.), false), - DocumentInputType::new("Status", TaggedValue::ImaginateStatus(ImaginateStatus::Idle), false), + DocumentInputType::value("Cached Data", TaggedValue::RcImage(None), false), + DocumentInputType::value("Percent Complete", TaggedValue::F64(0.), false), + DocumentInputType::value("Status", TaggedValue::ImaginateStatus(ImaginateStatus::Idle), false), ], - outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], + outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], properties: node_properties::imaginate_properties, -}; +}); pub fn resolve_document_node_type(name: &str) -> Option<&DocumentNodeType> { DOCUMENT_NODE_TYPES.iter().find(|node| node.name == name) @@ -580,7 +560,7 @@ impl DocumentNodeType { name: format!("{}_impl", self.name), // TODO: Allow inserting nodes that contain other nodes. implementation: DocumentNodeImplementation::Unresolved(ident.clone()), - inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(), + inputs: self.inputs.iter().map(|i| NodeInput::Network(i.default.ty())).collect(), metadata: DocumentNodeMetadata::default(), }, )] @@ -610,7 +590,7 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw outputs: vec![NodeOutput::new(1, 0)], nodes: [ resolve_document_node_type("Input").expect("Input node does not exist").to_document_node( - [NodeInput::Network, NodeInput::value(TaggedValue::DAffine2(DAffine2::IDENTITY), false)], + [NodeInput::Network(concrete!(Image)), NodeInput::value(TaggedValue::DAffine2(DAffine2::IDENTITY), false)], DocumentNodeMetadata::position((8, 4)), ), resolve_document_node_type("Output") diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index e84b197397..d168703955 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -231,6 +231,7 @@ impl MessageHandler { + debug!("ImaginateSetGeneratingStatus: {:?} {:?} {:?} {:?}", document_id, layer_path, node_path, percent); let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found")); if let Some(percentage) = percent { responses.push_back( diff --git a/editor/src/messages/tool/tool_messages/imaginate_tool.rs b/editor/src/messages/tool/tool_messages/imaginate_tool.rs index 3beb44b1a1..e600099ae4 100644 --- a/editor/src/messages/tool/tool_messages/imaginate_tool.rs +++ b/editor/src/messages/tool/tool_messages/imaginate_tool.rs @@ -120,7 +120,7 @@ impl Fsm for ImaginateToolFsmState { use graph_craft::document::*; - let imaginate_node_type = IMAGINATE_NODE; + let imaginate_node_type = &*IMAGINATE_NODE; let mut imaginate_inputs: Vec = imaginate_node_type.inputs.iter().map(|input| input.default.clone()).collect(); imaginate_inputs[0] = NodeInput::node(0, 0); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 6c8d658548..c86db4a3d8 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -39,7 +39,10 @@ impl NodeGraphExecutor { let proto_network = c.compile_single(network, true)?; assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?"); - self.executor.update(proto_network); + if let Err(e) = self.executor.update(proto_network) { + error!("Failed to update executor:\n{}", e); + return Err(e); + } use dyn_any::IntoDynAny; use graph_craft::executor::Executor; @@ -65,7 +68,7 @@ impl NodeGraphExecutor { } match &inner_network.nodes.get(&node_path[end]).unwrap().inputs[input_index] { // If the input is from a parent network then adjust the input index and continue iteration - NodeInput::Network => { + NodeInput::Network(_) => { input_index = inner_network .inputs .iter() diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index 16a453354f..bb32d07e1f 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -137,13 +137,13 @@ where } macro_rules! impl_type { - ($($id:ident$(<$($(($l:lifetime, $s:lifetime)),*|)?$($T:ident),*>)?),*) => { - $( - impl< $($($T: $crate::StaticTypeSized ,)*)?> $crate::StaticType for $id $(<$($($l,)*)?$($T, )*>)?{ - type Static = $id$(<$($($s,)*)?$(<$T as $crate::StaticTypeSized>::Static,)*>)?; - } - )* - }; + ($($id:ident$(<$($(($l:lifetime, $s:lifetime)),*|)?$($T:ident),*>)?),*) => { + $( + impl< $($($T: $crate::StaticTypeSized ,)*)?> $crate::StaticType for $id $(<$($($l,)*)?$($T, )*>)?{ + type Static = $id$(<$($($s,)*)?$(<$T as $crate::StaticTypeSized>::Static,)*>)?; + } + )* + }; } #[cfg(feature = "alloc")] @@ -206,12 +206,13 @@ use core::{ marker::{PhantomData, PhantomPinned}, mem::{ManuallyDrop, MaybeUninit}, num::Wrapping, + ops::Range, time::Duration, }; impl_type!( Option, Result, Cell, UnsafeCell, RefCell, MaybeUninit, - ManuallyDrop, PhantomData, PhantomPinned, Empty, + ManuallyDrop, PhantomData, PhantomPinned, Empty, Range, Wrapping, Duration, bool, f32, f64, char, u8, AtomicU8, u16, AtomicU16, u32, AtomicU32, u64, usize, AtomicUsize, i8, AtomicI8, i16, AtomicI16, i32, AtomicI32, i64, isize, AtomicIsize, @@ -265,19 +266,19 @@ fn test_tuple_of_boxes() { } macro_rules! impl_tuple { - (@rec $t:ident) => { }; - (@rec $_:ident $($t:ident)+) => { - impl_tuple! { @impl $($t)* } - impl_tuple! { @rec $($t)* } - }; - (@impl $($t:ident)*) => { - impl< $($t: StaticTypeSized,)*> StaticType for ($($t,)*) { - type Static = ($(<$t as $crate::StaticTypeSized>::Static,)*); - } - }; - ($($t:ident)*) => { - impl_tuple! { @rec _t $($t)* } - }; + (@rec $t:ident) => { }; + (@rec $_:ident $($t:ident)+) => { + impl_tuple! { @impl $($t)* } + impl_tuple! { @rec $($t)* } + }; + (@impl $($t:ident)*) => { + impl< $($t: StaticTypeSized,)*> StaticType for ($($t,)*) { + type Static = ($(<$t as $crate::StaticTypeSized>::Static,)*); + } + }; + ($($t:ident)*) => { + impl_tuple! { @rec _t $($t)* } + }; } impl_tuple! { diff --git a/node-graph/compilation-client/src/main.rs b/node-graph/compilation-client/src/main.rs index ffe9f25151..53cbdf09ba 100644 --- a/node-graph/compilation-client/src/main.rs +++ b/node-graph/compilation-client/src/main.rs @@ -1,8 +1,10 @@ use gpu_compiler_bin_wrapper::CompileRequest; +use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::*; -use graph_craft::proto::*; -use graph_craft::{concrete, generic}; + +use graph_craft::*; +use std::borrow::Cow; fn main() { let client = reqwest::blocking::Client::new(); @@ -17,7 +19,7 @@ fn main() { DocumentNode { name: "Inc Node".into(), inputs: vec![ - NodeInput::Network, + NodeInput::Network(concrete!(u32)), NodeInput::Value { tagged_value: TaggedValue::U32(1), exposed: false, @@ -47,9 +49,9 @@ fn add_network() -> NodeNetwork { 0, DocumentNode { name: "Cons".into(), - inputs: vec![NodeInput::Network, NodeInput::Network], + inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), concrete!("u32")])), + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode")), }, ), ( @@ -58,7 +60,7 @@ fn add_network() -> NodeNetwork { name: "Add".into(), inputs: vec![NodeInput::node(0, 0)], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])), + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode")), }, ), ] diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index 79ca6142fa..c8ebf90509 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -std = ["dyn-any", "dyn-any/std"] +std = ["dyn-any", "dyn-any/std", "alloc"] default = ["async", "serde", "kurbo", "log", "std"] log = ["dep:log"] serde = ["dep:serde", "glam/serde"] @@ -17,9 +17,10 @@ gpu = ["spirv-std", "bytemuck", "glam/bytemuck", "dyn-any"] async = ["async-trait", "alloc"] nightly = [] alloc = ["dyn-any", "bezier-rs"] +type_id_logging = [] [dependencies] -dyn-any = {path = "../../libraries/dyn-any", features = ["derive"], optional = true, default-features = false } +dyn-any = {path = "../../libraries/dyn-any", features = ["derive", "glam"], optional = true, default-features = false } spirv-std = { git = "https://github.com/EmbarkStudios/rust-gpu", features = ["glam"] , optional = true} bytemuck = {version = "1.8", features = ["derive"], optional = true} @@ -34,4 +35,5 @@ kurbo = { git = "https://github.com/linebender/kurbo.git", features = [ glam = { version = "^0.22", default-features = false, features = ["scalar-math", "libm"]} node-macro = {path = "../node-macro"} specta.workspace = true +once_cell = { version = "1.17.0", default-features = false } # forma = { version = "0.1.0", package = "forma-render" } diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 4269e8940e..b5ec97d8c8 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -21,12 +21,52 @@ pub mod raster; #[cfg(feature = "alloc")] pub mod vector; +use core::any::TypeId; + // pub trait Node: for<'n> NodeIO<'n> { pub trait Node<'i, Input: 'i>: 'i { type Output: 'i; fn eval<'s: 'i>(&'s self, input: Input) -> Self::Output; } +#[cfg(feature = "alloc")] +mod types; +pub use types::*; + +pub trait NodeIO<'i, Input: 'i>: 'i + Node<'i, Input> +where + Self::Output: 'i + StaticType, + Input: 'i + StaticType, +{ + fn input_type(&self) -> TypeId { + TypeId::of::() + } + fn input_type_name(&self) -> &'static str { + core::any::type_name::() + } + fn output_type(&self) -> core::any::TypeId { + TypeId::of::<::Static>() + } + fn output_type_name(&self) -> &'static str { + core::any::type_name::() + } + #[cfg(feature = "alloc")] + fn to_node_io(&self, parameters: Vec) -> NodeIOTypes { + NodeIOTypes { + input: concrete!(::Static), + output: concrete!(::Static), + parameters, + } + } +} + +impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N +where + N::Output: 'i + StaticType, + I: 'i + StaticType, +{ +} + /*impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn for<'n> Node<'n, I, Output = O> { type Output = O; @@ -42,6 +82,8 @@ impl<'i, 'n: 'i, I: 'i, O: 'i> Node<'i, I> for &'n dyn for<'a> Node<'a, I, Outpu } } use core::pin::Pin; + +use dyn_any::StaticType; #[cfg(feature = "alloc")] impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin Node<'a, I, Output = O> + 'i>> { type Output = O; diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs new file mode 100644 index 0000000000..a25aa0dcc0 --- /dev/null +++ b/node-graph/gcore/src/types.rs @@ -0,0 +1,108 @@ +use core::any::TypeId; + +#[cfg(not(feature = "std"))] +pub use alloc::borrow::Cow; +#[cfg(feature = "std")] +pub use std::borrow::Cow; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NodeIOTypes { + pub input: Type, + pub output: Type, + pub parameters: Vec, +} + +impl NodeIOTypes { + pub fn new(input: Type, output: Type, parameters: Vec) -> Self { + Self { input, output, parameters } + } +} + +#[macro_export] +macro_rules! concrete { + ($type:ty) => { + Type::Concrete(TypeDescriptor { + id: Some(core::any::TypeId::of::<$type>()), + name: Cow::Borrowed(core::any::type_name::<$type>()), + }) + }; +} +#[macro_export] +macro_rules! generic { + ($type:ty) => {{ + Type::Generic(Cow::Borrowed(stringify!($type))) + }}; +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, specta::Type)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NodeIdentifier { + pub name: Cow<'static, str>, +} + +#[derive(Clone, Debug, Eq, specta::Type)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TypeDescriptor { + #[cfg_attr(feature = "serde", serde(skip))] + #[specta(skip)] + pub id: Option, + pub name: Cow<'static, str>, +} + +impl core::hash::Hash for TypeDescriptor { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl PartialEq for TypeDescriptor { + fn eq(&self, other: &Self) -> bool { + match (self.id, other.id) { + (Some(id), Some(other_id)) => id == other_id, + _ => { + warn!("TypeDescriptor::eq: comparing types without ids based on name"); + self.name == other.name + } + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, specta::Type)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Type { + Generic(Cow<'static, str>), + Concrete(TypeDescriptor), +} + +impl core::fmt::Debug for Type { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Generic(arg0) => f.write_fmt(format_args!("Generic({})", arg0)), + #[cfg(feature = "type_id_logging")] + Self::Concrete(arg0) => f.write_fmt(format_args!("Concrete({}, {:?}))", arg0.name, arg0.id)), + #[cfg(not(feature = "type_id_logging"))] + Self::Concrete(arg0) => f.write_fmt(format_args!("Concrete({})", arg0.name)), + } + } +} + +impl std::fmt::Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::Generic(name) => write!(f, "{}", name), + Type::Concrete(ty) => write!(f, "{}", ty.name), + } + } +} + +impl From<&'static str> for NodeIdentifier { + fn from(s: &'static str) -> Self { + NodeIdentifier { name: Cow::Borrowed(s) } + } +} + +impl NodeIdentifier { + pub const fn new(name: &'static str) -> Self { + NodeIdentifier { name: Cow::Borrowed(name) } + } +} diff --git a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs index 22beb46882..5b43966ce6 100644 --- a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs +++ b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs @@ -14,19 +14,19 @@ pub fn compile_spirv(network: &graph_craft::document::NodeNetwork, input_type: & .arg("--release") .arg("--manifest-path") .arg(manifest_path) - .current_dir(manifest_path.replace("Cargo.toml", "")) - .env_clear() - .envs(non_cargo_env_vars) + .current_dir(manifest_path.replace("Cargo.toml", "")) + .env_clear() + .envs(non_cargo_env_vars) .arg("--features") .arg(features) - .arg("--") - .arg(input_type) - .arg(output_type) - // TODO: handle None case properly - .arg(compile_dir.unwrap()) + .arg("--") + .arg(input_type) + .arg(output_type) + // TODO: handle None case properly + .arg(compile_dir.unwrap()) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) - .spawn()?; + .spawn()?; cargo_command.stdin.as_mut().unwrap().write_all(serialized_graph.as_bytes())?; let output = cargo_command.wait_with_output()?; diff --git a/node-graph/gpu-compiler/src/lib.rs b/node-graph/gpu-compiler/src/lib.rs index 1b70df54b9..1bb60825ac 100644 --- a/node-graph/gpu-compiler/src/lib.rs +++ b/node-graph/gpu-compiler/src/lib.rs @@ -88,16 +88,16 @@ pub fn serialize_gpu(network: &ProtoNetwork, input_type: &str, output_type: &str use spirv_builder::{MetadataPrintout, SpirvBuilder, SpirvMetadata}; pub fn compile(dir: &Path) -> Result { - dbg!(&dir); + dbg!(&dir); let result = SpirvBuilder::new(dir, "spirv-unknown-spv1.5") - .print_metadata(MetadataPrintout::DependencyOnly) - .multimodule(false) - .preserve_bindings(true) - .release(true) - //.relax_struct_store(true) - //.relax_block_layout(true) - .spirv_metadata(SpirvMetadata::Full) - .build()?; + .print_metadata(MetadataPrintout::DependencyOnly) + .multimodule(false) + .preserve_bindings(true) + .release(true) + //.relax_struct_store(true) + //.relax_block_layout(true) + .spirv_metadata(SpirvMetadata::Full) + .build()?; Ok(result) } diff --git a/node-graph/gpu-compiler/src/main.rs b/node-graph/gpu-compiler/src/main.rs index 4dd4d07f73..8bf6207219 100644 --- a/node-graph/gpu-compiler/src/main.rs +++ b/node-graph/gpu-compiler/src/main.rs @@ -1,18 +1,18 @@ -use graph_craft::document::NodeNetwork; use gpu_compiler as compiler; +use graph_craft::document::NodeNetwork; use std::io::Write; -fn main() -> anyhow::Result<()> { - println!("Starting Gpu Compiler!"); +fn main() -> anyhow::Result<()> { + println!("Starting GPU Compiler!"); let mut stdin = std::io::stdin(); let mut stdout = std::io::stdout(); let input_type = std::env::args().nth(1).expect("input type arg missing"); let output_type = std::env::args().nth(2).expect("output type arg missing"); let compile_dir = std::env::args().nth(3).map(|x| std::path::PathBuf::from(&x)).unwrap_or(tempfile::tempdir()?.into_path()); let network: NodeNetwork = serde_json::from_reader(&mut stdin)?; - let compiler = graph_craft::executor::Compiler{}; + let compiler = graph_craft::executor::Compiler {}; let proto_network = compiler.compile(network, true); - dbg!(&compile_dir); + dbg!(&compile_dir); let metadata = compiler::Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]); diff --git a/node-graph/gpu-compiler/src/templates/spirv-template.rs b/node-graph/gpu-compiler/src/templates/spirv-template.rs index c5a6626a5c..764f558b65 100644 --- a/node-graph/gpu-compiler/src/templates/spirv-template.rs +++ b/node-graph/gpu-compiler/src/templates/spirv-template.rs @@ -7,32 +7,32 @@ extern crate spirv_std; #[cfg(target_arch = "spirv")] pub mod gpu { - use super::*; - use spirv_std::spirv; - use spirv_std::glam::UVec3; + use super::*; + use spirv_std::spirv; + use spirv_std::glam::UVec3; - #[allow(unused)] - #[spirv(compute(threads({{compute_threads}})))] - pub fn eval ( - #[spirv(global_invocation_id)] global_id: UVec3, - #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] a: &[{{input_type}}], - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] y: &mut [{{output_type}}], - #[spirv(push_constant)] push_consts: &graphene_core::gpu::PushConstants, - ) { - let gid = global_id.x as usize; - // Only process up to n, which is the length of the buffers. - if global_id.x < push_consts.n { - y[gid] = node_graph(a[gid]); - } - } + #[allow(unused)] + #[spirv(compute(threads({{compute_threads}})))] + pub fn eval ( + #[spirv(global_invocation_id)] global_id: UVec3, + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] a: &[{{input_type}}], + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] y: &mut [{{output_type}}], + #[spirv(push_constant)] push_consts: &graphene_core::gpu::PushConstants, + ) { + let gid = global_id.x as usize; + // Only process up to n, which is the length of the buffers. + if global_id.x < push_consts.n { + y[gid] = node_graph(a[gid]); + } + } - fn node_graph(input: {{input_type}}) -> {{output_type}} { - use graphene_core::Node; + fn node_graph(input: {{input_type}}) -> {{output_type}} { + use graphene_core::Node; - {% for node in nodes %} - let {{node.id}} = {{node.fqn}}::new({% for arg in node.args %}{{arg}}, {% endfor %}); - {% endfor %} + {% for node in nodes %} + let {{node.id}} = {{node.fqn}}::new({% for arg in node.args %}{{arg}}, {% endfor %}); + {% endfor %} {{last_node}}.eval(input) - } + } } diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index c65bf048b8..f0caf1fe1b 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -11,7 +11,7 @@ serde = ["dep:serde", "graphene-core/serde", "glam/serde"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -graphene-core = { path = "../gcore", features = ["alloc"] } +graphene-core = { path = "../gcore", features = ["std"] } dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] } num-traits = "0.2" dyn-clone = "1.0" diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 650278afef..83ba93299b 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -1,13 +1,15 @@ use crate::document::value::TaggedValue; -use crate::generic; -use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNetwork, ProtoNode, ProtoNodeInput, Type}; +use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; +use graphene_core::{NodeIdentifier, Type}; use dyn_any::{DynAny, StaticType}; use glam::IVec2; +use graphene_core::TypeDescriptor; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaCha20Rng, }; +use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::sync::Mutex; @@ -59,7 +61,7 @@ impl DocumentNode { .inputs .iter() .enumerate() - .filter(|(_, input)| matches!(input, NodeInput::Network)) + .filter(|(_, input)| matches!(input, NodeInput::Network(_))) .nth(offset) .expect("no network input"); @@ -80,9 +82,9 @@ impl DocumentNode { assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode."); (ProtoNodeInput::Node(node_id), ConstructionArgs::Nodes(vec![])) } - NodeInput::Network => (ProtoNodeInput::Network, ConstructionArgs::Nodes(vec![])), + NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])), }; - assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network)), "recieved non resolved parameter"); + assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter"); assert!( !self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })), "recieved value as parameter. inupts: {:#?}, construction_args: {:#?}", @@ -129,12 +131,12 @@ impl DocumentNode { } } -#[derive(Clone, Debug, specta::Type)] +#[derive(Debug, Clone, PartialEq, Hash, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum NodeInput { Node { node_id: NodeId, output_index: usize }, Value { tagged_value: value::TaggedValue, exposed: bool }, - Network, + Network(Type), } impl NodeInput { @@ -153,17 +155,14 @@ impl NodeInput { match self { NodeInput::Node { .. } => true, NodeInput::Value { exposed, .. } => *exposed, - NodeInput::Network => false, + NodeInput::Network(_) => false, } } -} - -impl PartialEq for NodeInput { - fn eq(&self, other: &Self) -> bool { - match (&self, &other) { - (Self::Node { node_id: n0, output_index: o0 }, Self::Node { node_id: n1, output_index: o1 }) => n0 == n1 && o0 == o1, - (Self::Value { tagged_value: v1, .. }, Self::Value { tagged_value: v2, .. }) => v1 == v2, - _ => core::mem::discriminant(self) == core::mem::discriminant(other), + pub fn ty(&self) -> Type { + match self { + NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"), + NodeInput::Value { tagged_value, .. } => tagged_value.ty(), + NodeInput::Network(ty) => ty.clone(), } } } @@ -357,7 +356,7 @@ impl NodeNetwork { .unwrap_or_else(|| panic!("The node which was supposed to be flattened does not exist in the network, id {} network {:#?}", node, self)); if self.disabled.contains(&id) { - node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])); + node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()); node.inputs.drain(1..); self.nodes.insert(id, node); return; @@ -394,7 +393,7 @@ impl NodeNetwork { let value_node = DocumentNode { name, inputs: vec![NodeInput::Value { tagged_value, exposed }], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[generic!("T")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()), metadata: DocumentNodeMetadata::default(), }; assert!(!self.nodes.contains_key(&new_id)); @@ -402,7 +401,7 @@ impl NodeNetwork { let network_input = self.nodes.get_mut(network_input).unwrap(); network_input.populate_first_network_input(new_id, 0, *offset); } - NodeInput::Network => { + NodeInput::Network(_) => { *network_offsets.get_mut(network_input).unwrap() += 1; if let Some(index) = self.inputs.iter().position(|i| *i == id) { self.inputs[index] = *network_input; @@ -410,7 +409,7 @@ impl NodeNetwork { } } } - node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])); + node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()); node.inputs = inner_network .outputs .iter() @@ -419,6 +418,7 @@ impl NodeNetwork { output_index: node_output_index, }) .collect(); + for node_id in new_nodes { self.flatten_with_fns(node_id, map_ids, gen_id); } @@ -456,8 +456,8 @@ impl NodeNetwork { 0, DocumentNode { name: "Input".into(), - inputs: vec![NodeInput::Network], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), + inputs: vec![NodeInput::Network(concrete!(u32))], + implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()), metadata: DocumentNodeMetadata { position: (8, 4).into() }, }, ), @@ -466,7 +466,7 @@ impl NodeNetwork { DocumentNode { name: "Output".into(), inputs: vec![NodeInput::node(output_node_id, 0)], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()), metadata: DocumentNodeMetadata { position: (output_offset, 4).into() }, }, ), @@ -553,7 +553,8 @@ impl NodeNetwork { #[cfg(test)] mod test { use super::*; - use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNetwork, ProtoNode, ProtoNodeInput}; + use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; + use graphene_core::NodeIdentifier; fn gen_node_id() -> NodeId { static mut NODE_ID: NodeId = 3; @@ -572,9 +573,9 @@ mod test { 0, DocumentNode { name: "Cons".into(), - inputs: vec![NodeInput::Network, NodeInput::Network], + inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()), }, ), ( @@ -583,7 +584,7 @@ mod test { name: "Add".into(), inputs: vec![NodeInput::node(0, 0)], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()), }, ), ] @@ -605,9 +606,9 @@ mod test { 1, DocumentNode { name: "Cons".into(), - inputs: vec![NodeInput::Network, NodeInput::Network], + inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()), }, ), ( @@ -616,7 +617,7 @@ mod test { name: "Add".into(), inputs: vec![NodeInput::node(1, 0)], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()), }, ), ] @@ -637,7 +638,7 @@ mod test { DocumentNode { name: "Inc".into(), inputs: vec![ - NodeInput::Network, + NodeInput::Network(concrete!(u32)), NodeInput::Value { tagged_value: value::TaggedValue::U32(2), exposed: false, @@ -663,15 +664,15 @@ mod test { fn resolve_proto_node_add() { let document_node = DocumentNode { name: "Cons".into(), - inputs: vec![NodeInput::Network, NodeInput::node(0, 0)], + inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::node(0, 0)], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()), }; let proto_node = document_node.resolve_proto_node(); let reference = ProtoNode { - identifier: NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")]), - input: ProtoNodeInput::Network, + identifier: "graphene_core::structural::ConsNode".into(), + input: ProtoNodeInput::Network(concrete!(u32)), construction_args: ConstructionArgs::Nodes(vec![0]), }; assert_eq!(proto_node, reference); @@ -686,7 +687,7 @@ mod test { ( 1, ProtoNode { - identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]), + identifier: "graphene_core::ops::IdNode".into(), input: ProtoNodeInput::Node(11), construction_args: ConstructionArgs::Nodes(vec![]), }, @@ -694,15 +695,15 @@ mod test { ( 10, ProtoNode { - identifier: NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")]), - input: ProtoNodeInput::Network, + identifier: "graphene_core::structural::ConsNode".into(), + input: ProtoNodeInput::Network(concrete!(u32)), construction_args: ConstructionArgs::Nodes(vec![14]), }, ), ( 11, ProtoNode { - identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")]), + identifier: "graphene_core::ops::AddNode".into(), input: ProtoNodeInput::Node(10), construction_args: ConstructionArgs::Nodes(vec![]), }, @@ -731,16 +732,16 @@ mod test { name: "Inc".into(), inputs: vec![NodeInput::node(11, 0)], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()), }, ), ( 10, DocumentNode { name: "Cons".into(), - inputs: vec![NodeInput::Network, NodeInput::node(14, 0)], + inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::node(14, 0)], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()), }, ), ( @@ -752,7 +753,7 @@ mod test { exposed: false, }], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[generic!("T")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()), }, ), ( @@ -761,7 +762,7 @@ mod test { name: "Add".into(), inputs: vec![NodeInput::node(10, 0)], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])), + implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()), }, ), ] @@ -780,18 +781,18 @@ mod test { 1, DocumentNode { name: "Identity 1".into(), - inputs: vec![NodeInput::Network], + inputs: vec![NodeInput::Network(concrete!(u32))], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), }, ), ( 2, DocumentNode { name: "Identity 2".into(), - inputs: vec![NodeInput::Network], + inputs: vec![NodeInput::Network(concrete!(u32))], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), }, ), ] @@ -821,7 +822,7 @@ mod test { name: "Result".into(), inputs: vec![result_node_input], metadata: DocumentNodeMetadata::default(), - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])), + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), }, ), ] diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 89a0d8f929..cf0f051c36 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -3,7 +3,7 @@ use dyn_any::{DynAny, Upcast}; use dyn_clone::DynClone; pub use glam::{DAffine2, DVec2}; use graphene_core::raster::LuminanceCalculation; -use graphene_core::Node; +use graphene_core::{Node, Type}; use std::hash::Hash; pub use std::sync::Arc; @@ -142,6 +142,32 @@ impl<'a> TaggedValue { TaggedValue::LayerPath(x) => Box::new(x), } } + + pub fn ty(&self) -> Type { + use graphene_core::TypeDescriptor; + use std::borrow::Cow; + match self { + TaggedValue::None => concrete!(()), + TaggedValue::String(_) => concrete!(String), + TaggedValue::U32(_) => concrete!(u32), + TaggedValue::F32(_) => concrete!(f32), + TaggedValue::F64(_) => concrete!(f64), + TaggedValue::Bool(_) => concrete!(bool), + TaggedValue::DVec2(_) => concrete!(DVec2), + TaggedValue::OptionalDVec2(_) => concrete!(Option), + TaggedValue::Image(_) => concrete!(graphene_core::raster::Image), + TaggedValue::RcImage(_) => concrete!(Option>), + TaggedValue::Color(_) => concrete!(graphene_core::raster::Color), + TaggedValue::Subpath(_) => concrete!(graphene_core::vector::subpath::Subpath), + TaggedValue::RcSubpath(_) => concrete!(Arc), + TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod), + TaggedValue::ImaginateMaskStartingFill(_) => concrete!(ImaginateMaskStartingFill), + TaggedValue::ImaginateStatus(_) => concrete!(ImaginateStatus), + TaggedValue::LayerPath(_) => concrete!(Option>), + TaggedValue::DAffine2(_) => concrete!(DAffine2), + TaggedValue::LuminanceCalculation(_) => concrete!(LuminanceCalculation), + } + } } pub struct UpcastNode { diff --git a/node-graph/graph-craft/src/lib.rs b/node-graph/graph-craft/src/lib.rs index 7330f40310..dd2fe61a2f 100644 --- a/node-graph/graph-craft/src/lib.rs +++ b/node-graph/graph-craft/src/lib.rs @@ -1,6 +1,10 @@ #[macro_use] extern crate log; +#[macro_use] +extern crate graphene_core; +pub use graphene_core::{concrete, generic, NodeIdentifier, Type, TypeDescriptor}; + pub mod document; pub mod proto; diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 9aa0ed8026..7b234e9c41 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -4,87 +4,16 @@ use std::hash::Hash; use crate::document::value; use crate::document::NodeId; +use dyn_any::DynAny; +use graphene_core::*; +use std::pin::Pin; -#[macro_export] -macro_rules! concrete { - ($type:expr) => { - Type::Concrete(std::borrow::Cow::Borrowed($type)) - }; -} -#[macro_export] -macro_rules! generic { - ($type:expr) => { - Type::Generic(std::borrow::Cow::Borrowed($type)) - }; -} +pub type Any<'n> = Box + 'n>; +pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync; +pub type TypeErasedPinnedRef<'n> = Pin<&'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync)>; +pub type TypeErasedPinned<'n> = Pin NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync>>; -#[derive(Clone, Debug, PartialEq, specta::Type)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct NodeIdentifier { - pub name: std::borrow::Cow<'static, str>, - pub types: std::borrow::Cow<'static, [Type]>, -} - -impl NodeIdentifier { - pub fn fully_qualified_name(&self) -> String { - let mut name = String::new(); - name.push_str(self.name.as_ref()); - name.push('<'); - for t in self.types.as_ref() { - name.push_str(t.to_string().as_str()); - name.push_str(", "); - } - name.pop(); - name.pop(); - name.push('>'); - name - } -} - -#[derive(Clone, Debug, PartialEq, specta::Type)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Type { - Generic(std::borrow::Cow<'static, str>), - Concrete(std::borrow::Cow<'static, str>), -} - -impl From<&'static str> for Type { - fn from(s: &'static str) -> Self { - Type::Concrete(std::borrow::Cow::Borrowed(s)) - } -} -impl std::fmt::Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Type::Generic(name) => write!(f, "{}", name), - Type::Concrete(name) => write!(f, "{}", name), - } - } -} - -impl Type { - pub const fn from_str(concrete: &'static str) -> Self { - Type::Concrete(std::borrow::Cow::Borrowed(concrete)) - } -} - -impl From<&'static str> for NodeIdentifier { - fn from(s: &'static str) -> Self { - NodeIdentifier { - name: std::borrow::Cow::Borrowed(s), - types: std::borrow::Cow::Borrowed(&[]), - } - } -} - -impl NodeIdentifier { - pub const fn new(name: &'static str, types: &'static [Type]) -> Self { - NodeIdentifier { - name: std::borrow::Cow::Borrowed(name), - types: std::borrow::Cow::Borrowed(types), - } - } -} +pub type NodeConstructor = for<'a> fn(Vec>) -> TypeErasedPinned<'static>; #[derive(Debug, Default, PartialEq)] pub struct ProtoNetwork { @@ -140,11 +69,10 @@ pub struct ProtoNode { pub identifier: NodeIdentifier, } -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum ProtoNodeInput { None, - #[default] - Network, + Network(Type), Node(NodeId), } @@ -161,11 +89,14 @@ impl ProtoNode { pub fn stable_node_id(&self) -> Option { use std::hash::Hasher; let mut hasher = std::collections::hash_map::DefaultHasher::new(); - self.identifier.fully_qualified_name().hash(&mut hasher); + self.identifier.name.hash(&mut hasher); self.construction_args.hash(&mut hasher); match self.input { ProtoNodeInput::None => "none".hash(&mut hasher), - ProtoNodeInput::Network => "network".hash(&mut hasher), + ProtoNodeInput::Network(ref ty) => { + "network".hash(&mut hasher); + ty.hash(&mut hasher); + } ProtoNodeInput::Node(id) => id.hash(&mut hasher), }; Some(hasher.finish() as NodeId) @@ -173,7 +104,7 @@ impl ProtoNode { pub fn value(value: ConstructionArgs) -> Self { Self { - identifier: NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic(Cow::Borrowed("T"))]), + identifier: NodeIdentifier::new("graphene_core::value::ValueNode"), construction_args: value, input: ProtoNodeInput::None, } @@ -260,20 +191,22 @@ impl ProtoNetwork { } pub fn resolve_inputs(&mut self) { - while !self.resolve_inputs_impl() {} + let mut resolved = HashSet::new(); + while !self.resolve_inputs_impl(&mut resolved) {} } - fn resolve_inputs_impl(&mut self) -> bool { + fn resolve_inputs_impl(&mut self, resolved: &mut HashSet) -> bool { self.reorder_ids(); let mut lookup = self.nodes.iter().map(|(id, _)| (*id, *id)).collect::>(); let compose_node_id = self.nodes.len() as NodeId; - let inputs = self.nodes.iter().map(|(_, node)| node.input).collect::>(); + let inputs = self.nodes.iter().map(|(_, node)| node.input.clone()).collect::>(); - if let Some((input_node, id, input)) = self.nodes.iter_mut().find_map(|(id, node)| { + let resolved_lookup = resolved.clone(); + if let Some((input_node, id, input)) = self.nodes.iter_mut().filter(|(id, _)| !resolved_lookup.contains(id)).find_map(|(id, node)| { if let ProtoNodeInput::Node(input_node) = node.input { - node.input = ProtoNodeInput::None; + resolved.insert(*id); let pre_node_input = inputs.get(input_node as usize).expect("input node should exist"); - Some((input_node, *id, *pre_node_input)) + Some((input_node, *id, pre_node_input.clone())) } else { None } @@ -283,7 +216,7 @@ impl ProtoNetwork { self.nodes.push(( compose_node_id, ProtoNode { - identifier: NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>", &[generic!("T"), generic!("U")]), + identifier: NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), construction_args: ConstructionArgs::Nodes(vec![input_node, id]), input, }, @@ -373,6 +306,172 @@ impl ProtoNetwork { } } +/// The `TypingContext` is used to store the types of the nodes indexed by their stable node id. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct TypingContext { + lookup: Cow<'static, HashMap>>, + inferred: HashMap, + constructor: HashMap, +} + +impl TypingContext { + /// Creates a new `TypingContext` with the given lookup table. + pub fn new(lookup: &'static HashMap>) -> Self { + Self { + lookup: Cow::Borrowed(lookup), + ..Default::default() + } + } + + /// Updates the `TypingContext` wtih a given proto network. This will infer the types of the nodes + /// and store them in the `inferred` field. The proto network has to be topologically sorted + /// and contain fully resolved stable node ids. + pub fn update(&mut self, network: &ProtoNetwork) -> Result<(), String> { + for (id, node) in network.nodes.iter() { + self.infer(*id, node)?; + } + Ok(()) + } + + /// Returns the node constructor for a given node id. + pub fn constructor(&self, node_id: NodeId) -> Option { + self.constructor.get(&node_id).copied() + } + + /// Returns the inferred types for a given node id. + pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result { + let identifier = node.identifier.name.clone(); + + // Return the inferred type if it is already known + if let Some(infered) = self.inferred.get(&node_id) { + return Ok(infered.clone()); + } + + let parameters = match node.construction_args { + // If the node has a value parameter we can infer the return type from it + ConstructionArgs::Value(ref v) => { + assert!(matches!(node.input, ProtoNodeInput::None)); + let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![]); + self.inferred.insert(node_id, types.clone()); + return Ok(types); + } + // If the node has nodes as parameters we can infer the types from the node outputs + ConstructionArgs::Nodes(ref nodes) => nodes + .iter() + .map(|id| { + self.inferred + .get(id) + .ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context")) + .map(|node| node.output.clone()) + }) + .collect::, String>>()?, + }; + + // Get the node input type from the proto node declaration + let input = match node.input { + ProtoNodeInput::None => concrete!(()), + ProtoNodeInput::Network(ref ty) => ty.clone(), + ProtoNodeInput::Node(id) => { + let input = self + .inferred + .get(&id) + .ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))?; + input.output.clone() + } + }; + let impls = self.lookup.get(&node.identifier).ok_or(format!("No implementations found for {:?}", node.identifier))?; + + if matches!(input, Type::Generic(_)) { + return Err(format!("Generic types are not supported as inputs yet {:?} occured in {:?}", &input, node.identifier)); + } + if parameters.iter().any(|p| matches!(p, Type::Generic(_))) { + return Err(format!("Generic types are not supported in parameters: {:?} occured in {:?}", parameters, node.identifier)); + } + let covariant = |output, input| match (&output, &input) { + (Type::Concrete(t1), Type::Concrete(t2)) => t1 == t2, + (Type::Concrete(_), Type::Generic(_)) => true, + // TODO: relax this requirement when allowing generic types as inputs + (Type::Generic(_), _) => false, + }; + + // List of all implementations that match the input and parameter types + let valid_output_types = impls + .keys() + .filter(|node_io| covariant(input.clone(), node_io.input.clone()) && parameters.iter().zip(node_io.parameters.iter()).all(|(p1, p2)| covariant(p1.clone(), p2.clone()))) + .collect::>(); + + // Attempt to substitute generic types with concrete types and save the list of results + let substitution_results = valid_output_types + .iter() + .map(|node_io| { + collect_generics(node_io) + .iter() + .try_for_each(|generic| check_generic(node_io, &input, ¶meters, generic).map(|_| ())) + .map(|_| { + if let Type::Generic(out) = &node_io.output { + ((*node_io).clone(), check_generic(node_io, &input, ¶meters, out).unwrap()) + } else { + ((*node_io).clone(), node_io.output.clone()) + } + }) + }) + .collect::>(); + + // Collect all substitutions that are valid + let valid_impls = substitution_results.iter().filter_map(|result| result.as_ref().ok()).collect::>(); + + match valid_impls.as_slice() { + [] => { + dbg!(&self.inferred); + Err(format!( + "No implementations found for {identifier} with \ninput: {input:?} and \nparameters: {parameters:?}.\nOther Implementations found: {:?}", + impls, + )) + } + [(org_nio, output)] => { + let node_io = NodeIOTypes::new(input, (*output).clone(), parameters); + + // Save the inferred type + self.inferred.insert(node_id, node_io.clone()); + self.constructor.insert(node_id, impls[org_nio]); + Ok(node_io) + } + _ => Err(format!( + "Multiple implementations found for {identifier} with input {input:?} and parameters {parameters:?} (valid types: {valid_output_types:?}" + )), + } + } +} + +/// Returns a list of all generic types used in the node +fn collect_generics(types: &NodeIOTypes) -> Vec> { + let inputs = [&types.input].into_iter().chain(types.parameters.iter()); + let mut generics = inputs + .filter_map(|t| match t { + Type::Generic(out) => Some(out.clone()), + _ => None, + }) + .collect::>(); + if let Type::Generic(out) = &types.output { + generics.push(out.clone()); + } + generics.dedup(); + generics +} + +/// Checks if a generic type can be substituted with a concrete type and returns the concrete type +fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result { + let inputs = [(&types.input, input)].into_iter().chain(types.parameters.iter().zip(parameters.iter())); + let mut concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Type::Generic(input) if generic == input)); + let (_, out_ty) = concrete_inputs + .next() + .ok_or_else(|| format!("Generic output type {generic} is not dependent on input {input:?} or parameters {parameters:?}",))?; + if concrete_inputs.any(|(_, ty)| ty != out_ty) { + return Err(format!("Generic output type {generic} is dependent on multiple inputs or parameters",)); + } + Ok(out_ty.clone()) +} + #[cfg(test)] mod test { use super::*; @@ -436,12 +535,12 @@ mod test { assert_eq!( ids, vec![ - 17495035641492238530, - 14931179783740213471, - 2268573767208263092, - 14616574692620381527, - 12110007198416821768, - 11185814750012198757 + 15907139529964845467, + 17186311536944112733, + 1674503539363691855, + 10408773954839245246, + 1677533587730447846, + 6826908746727711035 ] ); } @@ -471,7 +570,7 @@ mod test { 10, ProtoNode { identifier: "cons".into(), - input: ProtoNodeInput::Network, + input: ProtoNodeInput::Network(concrete!(u32)), construction_args: ConstructionArgs::Nodes(vec![14]), }, ), diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 8e10c6c27d..89ee7efdd5 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -1,6 +1,8 @@ -use dyn_any::{DynAny, StaticType}; +use dyn_any::StaticType; +pub use graph_craft::proto::{Any, TypeErasedNode, TypeErasedPinned, TypeErasedPinnedRef}; +use graphene_core::NodeIO; pub use graphene_core::{generic, ops, Node}; -use std::{marker::PhantomData, pin::Pin}; +use std::marker::PhantomData; pub struct DynAnyNode { node: Node, @@ -39,17 +41,13 @@ impl<_I, _O, S0> DynAnyRefNode<_I, _O, S0> { } } -pub type TypeErasedNode<'n> = dyn for<'i> Node<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync; -pub type TypeErasedPinnedRef<'n> = Pin<&'n (dyn for<'i> Node<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync)>; -pub type TypeErasedPinned<'n> = Pin Node<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync>>; - pub trait IntoTypeErasedNode<'n> { fn into_type_erased(self) -> TypeErasedPinned<'n>; } impl<'n, N: 'n> IntoTypeErasedNode<'n> for N where - N: for<'i> Node<'i, Any<'i>, Output = Any<'i>> + Send + Sync + 'n, + N: for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + Send + Sync + 'n, { fn into_type_erased(self) -> TypeErasedPinned<'n> { Box::pin(self) @@ -70,7 +68,7 @@ impl Copy for DowncastNode {} #[node_macro::node_fn(DowncastNode<_O>)] fn downcast(input: Any<'input>, node: &'input N) -> _O where - N: Node<'input, Any<'input>, Output = Any<'input>>, + N: for<'any_input> Node<'any_input, Any<'any_input>, Output = Any<'any_input>> + 'input, { let node_name = core::any::type_name::(); let out = dyn_any::downcast(node.eval(input)).unwrap_or_else(|e| panic!("DowncastNode Input {e} in:\n{node_name}")); @@ -148,8 +146,6 @@ impl<'a> ComposeTypeErased<'a> { } } -pub type Any<'n> = Box + 'n>; - pub fn input_node(n: TypeErasedPinnedRef) -> DowncastBothNode<(), O> { DowncastBothNode::new(n) } diff --git a/node-graph/interpreted-executor/Cargo.toml b/node-graph/interpreted-executor/Cargo.toml index 65c76a52ca..3dfad3fc69 100644 --- a/node-graph/interpreted-executor/Cargo.toml +++ b/node-graph/interpreted-executor/Cargo.toml @@ -24,3 +24,4 @@ rand_chacha = "0.3.1" log = "0.4" serde = { version = "1", features = ["derive"], optional = true } glam = { version = "0.22" } +once_cell = "1.17.0" diff --git a/node-graph/interpreted-executor/src/executor.rs b/node-graph/interpreted-executor/src/executor.rs index c45dc30106..31a6624cc2 100644 --- a/node-graph/interpreted-executor/src/executor.rs +++ b/node-graph/interpreted-executor/src/executor.rs @@ -6,28 +6,44 @@ use dyn_any::StaticType; use graph_craft::document::value::UpcastNode; use graph_craft::document::NodeId; use graph_craft::executor::Executor; -use graph_craft::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; +use graph_craft::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, TypingContext}; use graphene_std::any::{Any, TypeErasedPinned, TypeErasedPinnedRef}; -use crate::node_registry::constrcut_node; +use crate::node_registry; -#[derive(Default, Debug, Clone)] +#[derive(Debug, Clone)] pub struct DynamicExecutor { output: NodeId, tree: BorrowTree, + typing_context: TypingContext, +} + +impl Default for DynamicExecutor { + fn default() -> Self { + Self { + output: Default::default(), + tree: Default::default(), + typing_context: TypingContext::new(&node_registry::NODE_REGISTRY), + } + } } impl DynamicExecutor { - pub fn new(proto_network: ProtoNetwork) -> Self { + pub fn new(proto_network: ProtoNetwork) -> Result { + let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY); + typing_context.update(&proto_network)?; let output = proto_network.output; - let tree = BorrowTree::new(proto_network); - Self { tree, output } + let tree = BorrowTree::new(proto_network, &typing_context)?; + + Ok(Self { tree, output, typing_context }) } - pub fn update(&mut self, proto_network: ProtoNetwork) { + pub fn update(&mut self, proto_network: ProtoNetwork) -> Result<(), String> { self.output = proto_network.output; + self.typing_context.update(&proto_network)?; trace!("setting output to {}", self.output); - self.tree.update(proto_network); + self.tree.update(proto_network, &self.typing_context)?; + Ok(()) } } @@ -38,7 +54,7 @@ impl Executor for DynamicExecutor { } pub struct NodeContainer<'n> { - node: TypeErasedPinned<'n>, + pub node: TypeErasedPinned<'n>, // the dependencies are only kept to ensure that the nodes are not dropped while still in use _dependencies: Vec>>, } @@ -50,6 +66,10 @@ impl<'a> core::fmt::Debug for NodeContainer<'a> { } impl<'a> NodeContainer<'a> { + pub fn new(node: TypeErasedPinned<'a>, _dependencies: Vec>>) -> Self { + Self { node, _dependencies } + } + /// Return a static reference to the TypeErasedNode /// # Safety /// This is unsafe because the returned reference is only valid as long as the NodeContainer is alive @@ -58,7 +78,7 @@ impl<'a> NodeContainer<'a> { } } impl NodeContainer<'static> { - unsafe fn static_ref(&self) -> TypeErasedPinnedRef<'static> { + pub unsafe fn static_ref(&self) -> TypeErasedPinnedRef<'static> { let s = &*(self as *const Self); *(&s.node.as_ref() as *const TypeErasedPinnedRef<'static>) } @@ -70,24 +90,24 @@ pub struct BorrowTree { } impl BorrowTree { - pub fn new(proto_network: ProtoNetwork) -> Self { + pub fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result { let mut nodes = BorrowTree::default(); for (id, node) in proto_network.nodes { - nodes.push_node(id, node) + nodes.push_node(id, node, typing_context)? } - nodes + Ok(nodes) } /// Pushes new nodes into the tree and return orphaned nodes - pub fn update(&mut self, proto_network: ProtoNetwork) -> Vec { + pub fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result, String> { let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect(); for (id, node) in proto_network.nodes { if !self.nodes.contains_key(&id) { - self.push_node(id, node); + self.push_node(id, node, typing_context)?; old_nodes.remove(&id); } } - old_nodes.into_iter().collect() + Ok(old_nodes.into_iter().collect()) } fn node_refs(&self, nodes: &[NodeId]) -> Vec> { @@ -120,16 +140,8 @@ impl BorrowTree { self.nodes.remove(&id); } - pub fn push_node(&mut self, id: NodeId, proto_node: ProtoNode) { - let ProtoNode { input, construction_args, identifier } = proto_node; - - assert!( - !matches!(&input, &ProtoNodeInput::Node(_)), - "Only nodes without inputs are supported. Any inputs should already be resolved by placing ComposeNodes {:?}, {:?}, {:?}", - identifier, - construction_args, - input, - ); + pub fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), String> { + let ProtoNode { construction_args, identifier, .. } = proto_node; match construction_args { ConstructionArgs::Value(value) => { @@ -141,7 +153,8 @@ impl BorrowTree { } ConstructionArgs::Nodes(ids) => { let construction_nodes = self.node_refs(&ids); - let node = constrcut_node(identifier, construction_nodes); + let constructor = typing_context.constructor(id).ok_or(format!("No constructor found for node {:?}", identifier))?; + let node = constructor(construction_nodes); let node = NodeContainer { node, _dependencies: self.node_deps(&ids), @@ -149,7 +162,8 @@ impl BorrowTree { let node = unsafe { node.erase_lifetime() }; self.store_node(Arc::new(node), id); } - } + }; + Ok(()) } } @@ -163,7 +177,7 @@ mod test { fn push_node() { let mut tree = BorrowTree::default(); let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32))); - tree.push_node(0, val_1_protonode); + tree.push_node(0, val_1_protonode, &TypingContext::default()).unwrap(); let _node = tree.get(0).unwrap(); assert_eq!(tree.eval(0, ()), Some(2u32)); } diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 822d0745ee..7c817daf62 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -6,8 +6,9 @@ pub mod node_registry; #[cfg(test)] mod tests { - use dyn_any::IntoDynAny; + use graphene_core::*; + use std::borrow::Cow; /* #[test] @@ -46,7 +47,7 @@ mod tests { #[test] fn execute_add() { use graph_craft::document::*; - use graph_craft::proto::*; + use graph_craft::*; fn add_network() -> NodeNetwork { @@ -58,8 +59,8 @@ mod tests { 0, DocumentNode { name: "Cons".into(), - inputs: vec![NodeInput::Network, NodeInput::Network], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode<_, _>", &[concrete!("u32"), concrete!("u32")])), + inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(&u32))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode<_, _>")), metadata: DocumentNodeMetadata::default(), }, ), @@ -68,7 +69,7 @@ mod tests { DocumentNode { name: "Add".into(), inputs: vec![NodeInput::node(0, 0)], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[concrete!("(u32, u32)")])), + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode")), metadata: DocumentNodeMetadata::default(), }, ), @@ -87,9 +88,9 @@ mod tests { DocumentNode { name: "Inc".into(), inputs: vec![ - NodeInput::Network, + NodeInput::Network(concrete!(u32)), NodeInput::Value { - tagged_value: value::TaggedValue::U32(1), + tagged_value: graph_craft::document::value::TaggedValue::U32(1u32), exposed: false, }, ], @@ -108,7 +109,7 @@ mod tests { let compiler = Compiler {}; let protograph = compiler.compile_single(network, true).expect("Graph should be generated"); - let exec = DynamicExecutor::new(protograph); + let exec = DynamicExecutor::new(protograph).unwrap_or_else(|e| panic!("Failed to create executor: {}", e)); let result = exec.execute(32_u32.into_dyn()).unwrap(); let val = *dyn_any::downcast::(result).unwrap(); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 9802e82ceb..400a9b97a9 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,296 +1,369 @@ -use glam::DAffine2; +use glam::{DAffine2, DVec2}; +use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus}; use graphene_core::ops::{CloneNode, IdNode, TypeNode}; +use once_cell::sync::Lazy; +use std::collections::HashMap; + use graphene_core::raster::color::Color; use graphene_core::raster::*; use graphene_core::structural::Then; use graphene_core::value::{ForgetNode, ValueNode}; +use graphene_core::{Node, NodeIO, NodeIOTypes}; -use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DowncastBothRefNode, DynAnyNode, IntoTypeErasedNode, TypeErasedPinned, TypeErasedPinnedRef}; +use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DowncastBothRefNode, DynAnyNode, IntoTypeErasedNode}; -use graph_craft::proto::NodeIdentifier; -use graph_craft::proto::Type; +use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor}; -type NodeConstructor = for<'a> fn(Vec>) -> TypeErasedPinned<'static>; +use graph_craft::proto::NodeConstructor; -use graph_craft::{concrete, generic}; +use graphene_core::{concrete, generic}; use graphene_std::memo::CacheNode; +use crate::executor::NodeContainer; + +use dyn_any::StaticType; + macro_rules! register_node { ($path:ty, input: $input:ty, params: [$($type:ty),*]) => { - ( {NodeIdentifier::new(stringify!($path), &[concrete!(stringify!($input)), $(concrete!(stringify!($type))),*])}, - |args| { - let mut args = args.clone(); - args.reverse(); - let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("not enough arguments provided to construct node"))),*); - let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - Box::pin(any) as TypeErasedPinned - }) + ( + NodeIdentifier::new(stringify!($path)), + |args| { + let mut args = args.clone(); + args.reverse(); + let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("not enough arguments provided to construct node"))),*); + let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + Box::pin(any) + },{ + let node = IdNode::new().into_type_erased(); + let node = NodeContainer::new(node, vec![]); + let _node = unsafe { node.erase_lifetime().static_ref() }; + let node = <$path>::new($(graphene_std::any::input_node::<$type>(_node)),*); + let params = vec![$(concrete!($type)),*]; + let mut node_io = <$path as NodeIO<'_, $input>>::to_node_io(&node, params); + node_io.input = concrete!(<$input as StaticType>::Static); + node_io + } + ) }; } macro_rules! raster_node { ($path:ty, params: [$($type:ty),*]) => { - ( {NodeIdentifier::new(stringify!($path), &[concrete!("Image"), $(concrete!(stringify!($type))),*])}, - |args| { - let mut args = args.clone(); - args.reverse(); - let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"))),*); - let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); - Box::pin(any) as TypeErasedPinned - }) + ( + NodeIdentifier::new(stringify!($path)), + |args| { + let mut args = args.clone(); + args.reverse(); + let node = <$path>::new($( + graphene_core::value::ClonedNode::new( + graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node")) + .eval(())) + ),*); + let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); + Box::pin(any) + }, { + let params = vec![$(concrete!($type)),*]; + NodeIOTypes::new(concrete!(Image), concrete!(Image), params) + } + ) }; } //TODO: turn into hashmap -static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[ - //register_node!(graphene_core::ops::IdNode, input: Any<'_>, params: []), - (NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]), |_| IdNode::new().into_type_erased()), - // TODO: create macro to impl for all types - register_node!(graphene_core::structural::ConsNode<_, _>, input: u32, params: [u32]), - register_node!(graphene_core::structural::ConsNode<_, _>, input: u32, params: [&u32]), - register_node!(graphene_core::structural::ConsNode<_, _>, input: &u32, params: [u32]), - register_node!(graphene_core::structural::ConsNode<_, _>, input: &u32, params: [&u32]), - register_node!(graphene_core::ops::AddNode, input: (u32, u32), params: []), - register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [u32]), - register_node!(graphene_core::ops::AddParameterNode<_>, input: &u32, params: [u32]), - register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [&u32]), - register_node!(graphene_core::ops::AddParameterNode<_>, input: &u32, params: [&u32]), - register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [f64]), - register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [f64]), - register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]), - register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]), - (NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>", &[generic!("T"), generic!("U")]), |args| { - let node = ComposeTypeErased::new(args[0], args[1]); - node.into_type_erased() - }), - (NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]), |_| IdNode::new().into_type_erased()), - // Filters - raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]), - raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]), - raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]), - raster_node!(graphene_core::raster::InvertRGBNode, params: []), - raster_node!(graphene_core::raster::ThresholdNode<_, _>, params: [LuminanceCalculation, f64]), - raster_node!(graphene_core::raster::VibranceNode<_>, params: [f64]), - raster_node!(graphene_core::raster::BrightnessContrastNode< _, _>, params: [f64, f64]), - raster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]), - raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f64]), - raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f64, f64, f64]), - (NodeIdentifier::new("graphene_core::structural::MapImageNode", &[]), |args| { - let map_fn: DowncastBothNode = DowncastBothNode::new(args[0]); - let node = graphene_std::raster::MapImageNode::new(ValueNode::new(map_fn)); - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - any.into_type_erased() - }), - ( - NodeIdentifier::new("graphene_std::raster::ImaginateNode<_>", &[concrete!("Image"), concrete!("Option>")]), - |args| { - let cached = graphene_std::any::input_node::>>(args[16]); - let node = graphene_std::raster::ImaginateNode::new(cached); - let any = DynAnyNode::new(ValueNode::new(node)); - any.into_type_erased() - }, - ), - (NodeIdentifier::new("graphene_core::raster::BlurNode", &[concrete!("Image")]), |args| { - let radius = DowncastBothNode::<(), u32>::new(args[0]); - let sigma = DowncastBothNode::<(), f64>::new(args[1]); - let image = DowncastBothRefNode::::new(args[2]); - let empty_image: ValueNode = ValueNode::new(Image::empty()); - let empty: TypeNode<_, (), Image> = TypeNode::new(empty_image.then(CloneNode::new())); - - //let image = &image as &dyn for<'a> Node<'a, (), Output = &'a Image>; - // dirty hack: we abuse that the cache node will ignore the input if it is evaluated a second time - let image = empty.then(image).then(ImageRefNode::new()); - - let window = WindowNode::new(radius, image.clone()); - let map_gaussian = MapSndNode::new(ValueNode::new(DistanceNode.then(GaussianNode::new(sigma)))); - let map_distances = MapNode::new(ValueNode::new(map_gaussian)); - let gaussian_iter = window.then(map_distances); - let avg = gaussian_iter.then(WeightedAvgNode::new()); - let avg: TypeNode<_, u32, Color> = TypeNode::new(avg); - let blur_iter = MapNode::new(ValueNode::new(avg)); - let pixel_iter = image.clone().then(ImageIndexIterNode::new()); - let blur = pixel_iter.then(blur_iter); - let collect = CollectNode {}; - let vec = blur.then(collect); - let new_image = MapImageSliceNode::new(vec); - let dimensions = image.then(ImageDimensionsNode::new()); - let dimensions: TypeNode<_, (), (u32, u32)> = TypeNode::new(dimensions); - let new_image = dimensions.then(new_image); - let new_image = ForgetNode::new().then(new_image); - let node: DynAnyNode<&Image, _, _> = DynAnyNode::new(ValueNode::new(new_image)); - node.into_type_erased() - }), - //register_node!(graphene_std::memo::CacheNode<_>, input: Image, params: []), - (NodeIdentifier::new("graphene_std::memo::CacheNode", &[concrete!("Image")]), |_| { - let node: CacheNode = graphene_std::memo::CacheNode::new(); - let any = graphene_std::any::DynAnyRefNode::new(node); - any.into_type_erased() - }), - register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), - register_node!(graphene_std::raster::ImageFrameNode<_>, input: Image, params: [DAffine2]), - /* - (NodeIdentifier::new("graphene_std::raster::ImageNode", &[concrete!("&str")]), |_proto_node, stack| { - stack.push_fn(|_nodes| { - let image = FnNode::new(|s: &str| graphene_std::raster::image_node::<&str>().eval(s).unwrap()); - let node: DynAnyNode<_, &str, _, _> = DynAnyNode::new(image); - node.into_type_erased() - }) - }), - (NodeIdentifier::new("graphene_std::raster::ExportImageNode", &[concrete!("&str")]), |proto_node, stack| { - stack.push_fn(|nodes| { - let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap(); - - let image = FnNode::new(|input: (Image, &str)| graphene_std::raster::export_image_node().eval(input).unwrap()); - let node: DynAnyNode<_, (Image, &str), _, _> = DynAnyNode::new(image); - let node = (pre_node).then(node); - node.into_type_erased() - }) - }), +fn node_registry() -> HashMap> { + let node_types: Vec<(NodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ + //register_node!(graphene_core::ops::IdNode, input: Any<'_>, params: []), ( - NodeIdentifier::new("graphene_core::structural::ConsNode", &[concrete!("Image"), concrete!("&str")]), - |proto_node, stack| { - let node_id = proto_node.input.unwrap_node() as usize; - if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args { - stack.push_fn(move |nodes| { - let pre_node = nodes.get(node_id).unwrap(); - let cons_node_arg = nodes.get(cons_node_arg[0] as usize).unwrap(); + NodeIdentifier::new("graphene_core::ops::IdNode"), + |_| IdNode::new().into_type_erased(), + NodeIOTypes::new(generic!(I), generic!(I), vec![]), + ), + // TODO: create macro to impl for all types + register_node!(graphene_core::structural::ConsNode<_, _>, input: u32, params: [u32]), + register_node!(graphene_core::structural::ConsNode<_, _>, input: u32, params: [&u32]), + register_node!(graphene_core::structural::ConsNode<_, _>, input: &u32, params: [u32]), + register_node!(graphene_core::structural::ConsNode<_, _>, input: &u32, params: [&u32]), + register_node!(graphene_core::ops::AddNode, input: (u32, u32), params: []), + register_node!(graphene_core::ops::AddNode, input: (u32, &u32), params: []), + register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [u32]), + register_node!(graphene_core::ops::AddParameterNode<_>, input: &u32, params: [u32]), + register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [&u32]), + register_node!(graphene_core::ops::AddParameterNode<_>, input: &u32, params: [&u32]), + register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [f64]), + register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [f64]), + register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]), + register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]), + ( + NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), + |args| { + let node = ComposeTypeErased::new(args[0], args[1]); + node.into_type_erased() + }, + NodeIOTypes::new(generic!(T), generic!(U), vec![generic!(V), generic!(U)]), + ), + // Filters + raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]), + raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]), + raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]), + raster_node!(graphene_core::raster::InvertRGBNode, params: []), + raster_node!(graphene_core::raster::ThresholdNode<_, _>, params: [LuminanceCalculation, f64]), + raster_node!(graphene_core::raster::VibranceNode<_>, params: [f64]), + raster_node!(graphene_core::raster::BrightnessContrastNode< _, _>, params: [f64, f64]), + raster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]), + raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f64]), + raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f64, f64, f64]), + ( + NodeIdentifier::new("graphene_core::structural::MapImageNode"), + |args| { + let map_fn: DowncastBothNode = DowncastBothNode::new(args[0]); + let node = graphene_std::raster::MapImageNode::new(ValueNode::new(map_fn)); + let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + any.into_type_erased() + }, + NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![]), + ), + ( + NodeIdentifier::new("graphene_std::raster::ImaginateNode<_>"), + |args| { + let cached = graphene_std::any::input_node::>>(args[16]); + let node = graphene_std::raster::ImaginateNode::new(cached); + let any = DynAnyNode::new(ValueNode::new(node)); + any.into_type_erased() + }, + NodeIOTypes::new( + concrete!(Image), + concrete!(Image), + vec![ + concrete!(DAffine2), + concrete!(f64), + concrete!(Option), + concrete!(f64), + concrete!(ImaginateSamplingMethod), + concrete!(f64), + concrete!(String), + concrete!(String), + concrete!(bool), + concrete!(f64), + concrete!(Option>), + concrete!(bool), + concrete!(f64), + concrete!(ImaginateMaskStartingFill), + concrete!(bool), + concrete!(bool), + concrete!(Option>), + concrete!(f64), + concrete!(ImaginateStatus), + ], + ), + ), + ( + NodeIdentifier::new("graphene_core::raster::BlurNode"), + |args| { + let radius = DowncastBothNode::<(), u32>::new(args[0]); + let sigma = DowncastBothNode::<(), f64>::new(args[1]); + let image = DowncastBothRefNode::::new(args[2]); + let empty_image: ValueNode = ValueNode::new(Image::empty()); + let empty: TypeNode<_, (), Image> = TypeNode::new(empty_image.then(CloneNode::new())); - let cons_node = ConsNode::new(DowncastNode::<_, &str>::new(cons_node_arg)); - let node: DynAnyNode<_, Image, _, _> = DynAnyNode::new(cons_node); - let node = (pre_node).then(node); + //let image = &image as &dyn for<'a> Node<'a, (), Output = &'a Image>; + // dirty hack: we abuse that the cache node will ignore the input if it is evaluated a second time + let image = empty.then(image).then(ImageRefNode::new()); + + let window = WindowNode::new(radius, image.clone()); + let map_gaussian = MapSndNode::new(ValueNode::new(DistanceNode.then(GaussianNode::new(sigma)))); + let map_distances = MapNode::new(ValueNode::new(map_gaussian)); + let gaussian_iter = window.then(map_distances); + let avg = gaussian_iter.then(WeightedAvgNode::new()); + let avg: TypeNode<_, u32, Color> = TypeNode::new(avg); + let blur_iter = MapNode::new(ValueNode::new(avg)); + let pixel_iter = image.clone().then(ImageIndexIterNode::new()); + let blur = pixel_iter.then(blur_iter); + let collect = CollectNode {}; + let vec = blur.then(collect); + let new_image = MapImageSliceNode::new(vec); + let dimensions = image.then(ImageDimensionsNode::new()); + let dimensions: TypeNode<_, (), (u32, u32)> = TypeNode::new(dimensions); + let new_image = dimensions.then(new_image); + let new_image = ForgetNode::new().then(new_image); + let node: DynAnyNode<&Image, _, _> = DynAnyNode::new(ValueNode::new(new_image)); + node.into_type_erased() + }, + NodeIOTypes::new(concrete!(Image), concrete!(Image), vec![concrete!(u32), concrete!(f64)]), + ), + //register_node!(graphene_std::memo::CacheNode<_>, input: Image, params: []), + ( + NodeIdentifier::new("graphene_std::memo::CacheNode"), + |_| { + let node: CacheNode = graphene_std::memo::CacheNode::new(); + let any = graphene_std::any::DynAnyRefNode::new(node); + any.into_type_erased() + }, + NodeIOTypes::new(concrete!(Image), concrete!(&Image), vec![]), + ), + register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), + register_node!(graphene_std::raster::ImageFrameNode<_>, input: Image, params: [DAffine2]), + /* + (NodeIdentifier::new("graphene_std::raster::ImageNode", &[concrete!("&str")]), |_proto_node, stack| { + stack.push_fn(|_nodes| { + let image = FnNode::new(|s: &str| graphene_std::raster::image_node::<&str>().eval(s).unwrap()); + let node: DynAnyNode<_, &str, _, _> = DynAnyNode::new(image); + node.into_type_erased() + }) + }), + (NodeIdentifier::new("graphene_std::raster::ExportImageNode", &[concrete!("&str")]), |proto_node, stack| { + stack.push_fn(|nodes| { + let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap(); + + let image = FnNode::new(|input: (Image, &str)| graphene_std::raster::export_image_node().eval(input).unwrap()); + let node: DynAnyNode<_, (Image, &str), _, _> = DynAnyNode::new(image); + let node = (pre_node).then(node); + node.into_type_erased() + }) + }), + ( + NodeIdentifier::new("graphene_core::structural::ConsNode", &[concrete!("Image"), concrete!("&str")]), + |proto_node, stack| { + let node_id = proto_node.input.unwrap_node() as usize; + if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args { + stack.push_fn(move |nodes| { + let pre_node = nodes.get(node_id).unwrap(); + let cons_node_arg = nodes.get(cons_node_arg[0] as usize).unwrap(); + + let cons_node = ConsNode::new(DowncastNode::<_, &str>::new(cons_node_arg)); + let node: DynAnyNode<_, Image, _, _> = DynAnyNode::new(cons_node); + let node = (pre_node).then(node); + node.into_type_erased() + }) + } else { + unimplemented!() + } + }, + ), + (NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]), |_proto_node, stack| { + stack.push_fn(|_nodes| DynAnyNode::new(graphene_std::vector::generator_nodes::UnitCircleGenerator).into_type_erased()) + }), + (NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]), |_proto_node, stack| { + stack.push_fn(|_nodes| DynAnyNode::new(graphene_std::vector::generator_nodes::UnitSquareGenerator).into_type_erased()) + }), + (NodeIdentifier::new("graphene_std::vector::generator_nodes::BlitSubpath", &[]), |proto_node, stack| { + stack.push_fn(move |nodes| { + let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("BlitSubpath without subpath input") }; + let value_node = nodes.get(construction_nodes[0] as usize).unwrap(); + let input_node: DowncastBothNode<_, (), Subpath> = DowncastBothNode::new(value_node); + let node = DynAnyNode::new(graphene_std::vector::generator_nodes::BlitSubpath::new(input_node)); + + if let ProtoNodeInput::Node(node_id) = proto_node.input { + let pre_node = nodes.get(node_id as usize).unwrap(); + (pre_node).then(node).into_type_erased() + } else { node.into_type_erased() - }) - } else { - unimplemented!() - } - }, - ), - (NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]), |_proto_node, stack| { - stack.push_fn(|_nodes| DynAnyNode::new(graphene_std::vector::generator_nodes::UnitCircleGenerator).into_type_erased()) - }), - (NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]), |_proto_node, stack| { - stack.push_fn(|_nodes| DynAnyNode::new(graphene_std::vector::generator_nodes::UnitSquareGenerator).into_type_erased()) - }), - (NodeIdentifier::new("graphene_std::vector::generator_nodes::BlitSubpath", &[]), |proto_node, stack| { - stack.push_fn(move |nodes| { - let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("BlitSubpath without subpath input") }; - let value_node = nodes.get(construction_nodes[0] as usize).unwrap(); - let input_node: DowncastBothNode<_, (), Subpath> = DowncastBothNode::new(value_node); - let node = DynAnyNode::new(graphene_std::vector::generator_nodes::BlitSubpath::new(input_node)); + } + }) + }), + (NodeIdentifier::new("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]), |proto_node, stack| { + stack.push_fn(move |nodes| { + let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("TransformSubpathNode without subpath input") }; + let translate_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap()); + let rotate_node: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[1] as usize).unwrap()); + let scale_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[2] as usize).unwrap()); + let shear_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[3] as usize).unwrap()); - if let ProtoNodeInput::Node(node_id) = proto_node.input { - let pre_node = nodes.get(node_id as usize).unwrap(); - (pre_node).then(node).into_type_erased() - } else { - node.into_type_erased() - } - }) - }), - (NodeIdentifier::new("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]), |proto_node, stack| { - stack.push_fn(move |nodes| { - let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("TransformSubpathNode without subpath input") }; - let translate_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap()); - let rotate_node: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[1] as usize).unwrap()); - let scale_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[2] as usize).unwrap()); - let shear_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[3] as usize).unwrap()); + let node = DynAnyNode::new(graphene_std::vector::generator_nodes::TransformSubpathNode::new(translate_node, rotate_node, scale_node, shear_node)); - let node = DynAnyNode::new(graphene_std::vector::generator_nodes::TransformSubpathNode::new(translate_node, rotate_node, scale_node, shear_node)); + if let ProtoNodeInput::Node(node_id) = proto_node.input { + let pre_node = nodes.get(node_id as usize).unwrap(); + (pre_node).then(node).into_type_erased() + } else { + node.into_type_erased() + } + }) + }), + #[cfg(feature = "gpu")] + ( + NodeIdentifier::new("graphene_std::executor::MapGpuNode", &[concrete!("&TypeErasedNode"), concrete!("Color"), concrete!("Color")]), + |proto_node, stack| { + if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args { + stack.push_fn(move |nodes| { + info!("Map image Depending upon id {:?}", operation_node_id); + let operation_node = nodes.get(operation_node_id[0] as usize).unwrap(); + let input_node: DowncastBothNode<_, (), &graph_craft::document::NodeNetwork> = DowncastBothNode::new(operation_node); + let map_node: graphene_std::executor::MapGpuNode<_, Vec, u32, u32> = graphene_std::executor::MapGpuNode::new(input_node); + let map_node = DynAnyNode::new(map_node); - if let ProtoNodeInput::Node(node_id) = proto_node.input { - let pre_node = nodes.get(node_id as usize).unwrap(); - (pre_node).then(node).into_type_erased() - } else { - node.into_type_erased() - } - }) - }), - #[cfg(feature = "gpu")] - ( - NodeIdentifier::new("graphene_std::executor::MapGpuNode", &[concrete!("&TypeErasedNode"), concrete!("Color"), concrete!("Color")]), - |proto_node, stack| { - if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args { - stack.push_fn(move |nodes| { - info!("Map image Depending upon id {:?}", operation_node_id); - let operation_node = nodes.get(operation_node_id[0] as usize).unwrap(); - let input_node: DowncastBothNode<_, (), &graph_craft::document::NodeNetwork> = DowncastBothNode::new(operation_node); - let map_node: graphene_std::executor::MapGpuNode<_, Vec, u32, u32> = graphene_std::executor::MapGpuNode::new(input_node); - let map_node = DynAnyNode::new(map_node); + if let ProtoNodeInput::Node(node_id) = proto_node.input { + let pre_node = nodes.get(node_id as usize).unwrap(); + (pre_node).then(map_node).into_type_erased() + } else { + map_node.into_type_erased() + } + }) + } else { + unimplemented!() + } + }, + ), + #[cfg(feature = "gpu")] + ( + NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode", &[concrete!("&TypeErasedNode")]), + |proto_node, stack| { + if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args { + stack.push_fn(move |nodes| { + info!("Map image Depending upon id {:?}", operation_node_id); + let operation_node = nodes.get(operation_node_id[0] as usize).unwrap(); + let input_node: DowncastBothNode<_, (), String> = DowncastBothNode::new(operation_node); + let map_node = graphene_std::executor::MapGpuSingleImageNode(input_node); + let map_node = DynAnyNode::new(map_node); - if let ProtoNodeInput::Node(node_id) = proto_node.input { - let pre_node = nodes.get(node_id as usize).unwrap(); - (pre_node).then(map_node).into_type_erased() - } else { - map_node.into_type_erased() - } - }) - } else { - unimplemented!() - } - }, - ), - #[cfg(feature = "gpu")] - ( - NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode", &[concrete!("&TypeErasedNode")]), - |proto_node, stack| { - if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args { - stack.push_fn(move |nodes| { - info!("Map image Depending upon id {:?}", operation_node_id); - let operation_node = nodes.get(operation_node_id[0] as usize).unwrap(); - let input_node: DowncastBothNode<_, (), String> = DowncastBothNode::new(operation_node); - let map_node = graphene_std::executor::MapGpuSingleImageNode(input_node); - let map_node = DynAnyNode::new(map_node); + if let ProtoNodeInput::Node(node_id) = proto_node.input { + let pre_node = nodes.get(node_id as usize).unwrap(); + (pre_node).then(map_node).into_type_erased() + } else { + map_node.into_type_erased() + } + }) + } else { + unimplemented!() + } + }, + ), + #[cfg(feature = "quantization")] + ( + NodeIdentifier::new("graphene_std::quantization::GenerateQuantizationNode", &[concrete!("&TypeErasedNode")]), + |proto_node, stack| { + if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args { + stack.push_fn(move |nodes| { + info!("Quantization Depending upon id {:?}", operation_node_id); + let samples_node = nodes.get(operation_node_id[0] as usize).unwrap(); + let index_node = nodes.get(operation_node_id[1] as usize).unwrap(); + let samples_node: DowncastBothNode<_, (), u32> = DowncastBothNode::new(samples_node); + let index_node: DowncastBothNode<_, (), u32> = DowncastBothNode::new(index_node); + let map_node = graphene_std::quantization::GenerateQuantizationNode::new(samples_node, index_node); + let map_node = DynAnyNode::new(map_node); - if let ProtoNodeInput::Node(node_id) = proto_node.input { - let pre_node = nodes.get(node_id as usize).unwrap(); - (pre_node).then(map_node).into_type_erased() - } else { - map_node.into_type_erased() - } - }) - } else { - unimplemented!() - } - }, - ), - #[cfg(feature = "quantization")] - ( - NodeIdentifier::new("graphene_std::quantization::GenerateQuantizationNode", &[concrete!("&TypeErasedNode")]), - |proto_node, stack| { - if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args { - stack.push_fn(move |nodes| { - info!("Quantization Depending upon id {:?}", operation_node_id); - let samples_node = nodes.get(operation_node_id[0] as usize).unwrap(); - let index_node = nodes.get(operation_node_id[1] as usize).unwrap(); - let samples_node: DowncastBothNode<_, (), u32> = DowncastBothNode::new(samples_node); - let index_node: DowncastBothNode<_, (), u32> = DowncastBothNode::new(index_node); - let map_node = graphene_std::quantization::GenerateQuantizationNode::new(samples_node, index_node); - let map_node = DynAnyNode::new(map_node); - - if let ProtoNodeInput::Node(node_id) = proto_node.input { - let pre_node = nodes.get(node_id as usize).unwrap(); - (pre_node).then(map_node).into_type_erased() - } else { - map_node.into_type_erased() - } - }) - } else { - unimplemented!() - } - }, - ), - <<<<<<< HEAD - */ -]; - -pub fn constrcut_node<'a>(ident: NodeIdentifier, construction_args: Vec>) -> TypeErasedPinned<'a> { - if let Some((_id, f)) = NODE_REGISTRY.iter().find(|(id, _)| *id == ident) { - f(construction_args) - } else { - let other_types = NODE_REGISTRY.iter().map(|(id, _)| id).filter(|id| id.name.as_ref() == ident.name.as_ref()).collect::>(); - panic!("NodeImplementation: {:?} not found in Registry. Types for which the node is implemented:\n {:#?}", ident, other_types); + if let ProtoNodeInput::Node(node_id) = proto_node.input { + let pre_node = nodes.get(node_id as usize).unwrap(); + (pre_node).then(map_node).into_type_erased() + } else { + map_node.into_type_erased() + } + }) + } else { + unimplemented!() + } + }, + ), + <<<<<<< HEAD + */ + ]; + let mut map: HashMap> = HashMap::new(); + for (id, c, types) in node_types { + map.entry(id).or_default().insert(types.clone(), c); } + map } +pub static NODE_REGISTRY: Lazy>> = Lazy::new(|| node_registry()); + /* #[cfg(test)] mod protograph_testing {