diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 4c7d1aca..43921aa1 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -623,7 +623,7 @@ impl MessageHandler Self::Boolean, TaggedValue::DVec2(_) => Self::Vector, TaggedValue::Image(_) => Self::Raster, + TaggedValue::ImageFrame(_) => Self::Raster, TaggedValue::Color(_) => Self::Color, TaggedValue::RcSubpath(_) | TaggedValue::Subpath(_) => Self::Subpath, _ => Self::General, @@ -279,6 +280,8 @@ impl NodeGraphMessageHandler { if let NodeInput::Node { node_id: link_start, output_index: link_start_index, + // TODO: add ui for lambdas + lambda, } = *input { Some(FrontendNodeLink { 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 fa2f7a79..7c444e6f 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 @@ -108,7 +108,7 @@ fn static_nodes() -> Vec { name: "Image", category: "Ignore", identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), - inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), false)], + inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::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"), }, @@ -130,20 +130,12 @@ fn static_nodes() -> Vec { 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!(ImageFrame))], - 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(), - }, - ] + nodes: [DocumentNode { + name: "Identity".to_string(), + inputs: vec![NodeInput::Network(concrete!(ImageFrame))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")), + metadata: Default::default(), + }] .into_iter() .enumerate() .map(|(id, node)| (id as NodeId, node)) @@ -158,24 +150,97 @@ fn static_nodes() -> Vec { }, DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false), ], + outputs: vec![DocumentOutputType { + name: "Image Frame", + data_type: FrontendGraphDataType::Raster, + }], + properties: node_properties::input_properties, + }, + DocumentNodeType { + name: "Begin Scope", + category: "Structural", + identifier: NodeImplementation::DocumentNode(NodeNetwork { + inputs: vec![0, 2], + outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(3, 0)], + nodes: [ + DocumentNode { + name: "SetNode".to_string(), + inputs: vec![NodeInput::Network(concrete!(ImageFrame))], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")), + metadata: Default::default(), + }, + DocumentNode { + name: "LetNode".to_string(), + inputs: vec![NodeInput::node(0, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::LetNode<_>")), + metadata: Default::default(), + }, + DocumentNode { + name: "RefNode".to_string(), + inputs: vec![NodeInput::Network(concrete!(())), NodeInput::lambda(1, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::RefNode<_, _>")), + metadata: Default::default(), + }, + DocumentNode { + name: "CloneNode".to_string(), + inputs: vec![NodeInput::node(2, 0)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::CloneNode<_>")), + metadata: Default::default(), + }, + ] + .into_iter() + .enumerate() + .map(|(id, node)| (id as NodeId, node)) + .collect(), + + ..Default::default() + }), + inputs: vec![DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + }], outputs: vec![ DocumentOutputType { - name: "Image Frame", - data_type: FrontendGraphDataType::Raster, + name: "Scope", + data_type: FrontendGraphDataType::General, }, DocumentOutputType { - name: "Transform", - data_type: FrontendGraphDataType::Number, + name: "Binding", + data_type: FrontendGraphDataType::Raster, }, ], - properties: node_properties::input_properties, + properties: |_document_node, _node_id, _context| node_properties::string_properties("Binds the input in a local scope as a variable"), + }, + DocumentNodeType { + name: "End Scope", + category: "Structural", + identifier: NodeImplementation::proto("graphene_std::memo::EndLetNode<_>"), + inputs: vec![ + DocumentInputType { + name: "Scope", + data_type: FrontendGraphDataType::General, + default: NodeInput::value(TaggedValue::None, true), + }, + DocumentInputType { + name: "Data", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + }, + ], + outputs: vec![DocumentOutputType { + name: "Frame", + data_type: FrontendGraphDataType::Raster, + }], + + properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame"), }, DocumentNodeType { name: "Output", category: "Ignore", identifier: NodeImplementation::proto("graphene_core::ops::IdNode"), inputs: vec![DocumentInputType { - name: "In", + name: "Output", data_type: FrontendGraphDataType::Raster, default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), }], @@ -198,8 +263,8 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::BlendNode<_, _, _, _>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), - DocumentInputType::value("Second", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), + DocumentInputType::value("Second", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("BlendMode", TaggedValue::BlendMode(BlendMode::Normal), false), DocumentInputType::value("Opacity", TaggedValue::F64(100.), false), ], @@ -214,7 +279,7 @@ fn static_nodes() -> Vec { DocumentInputType { name: "Image", data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), }, DocumentInputType { name: "Shadows", @@ -253,7 +318,7 @@ fn static_nodes() -> Vec { DocumentInputType { name: "Image", data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), }, DocumentInputType { name: "Tint", @@ -299,7 +364,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::LuminanceNode<_>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false), ], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], @@ -336,7 +401,7 @@ fn static_nodes() -> Vec { ..Default::default() }), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Radius", TaggedValue::U32(3), false), DocumentInputType::value("Sigma", TaggedValue::F64(1.), false), ], @@ -376,7 +441,7 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true)], + inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], properties: node_properties::no_properties, }, @@ -386,7 +451,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"), inputs: vec![ - DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::new("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType { name: "Path", data_type: FrontendGraphDataType::Text, @@ -405,7 +470,7 @@ fn static_nodes() -> Vec { DocumentInputType { name: "Image", data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), }, DocumentInputType { name: "samples", @@ -425,7 +490,7 @@ fn static_nodes() -> Vec { name: "Invert RGB", category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::InvertRGBNode"), - inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true)], + inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], properties: node_properties::no_properties, }, @@ -434,7 +499,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::HueSaturationNode<_, _, _>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::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), @@ -447,7 +512,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::BrightnessContrastNode<_, _>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Brightness", TaggedValue::F64(0.), false), DocumentInputType::value("Contrast", TaggedValue::F64(0.), false), ], @@ -459,7 +524,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::ThresholdNode<_, _>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false), DocumentInputType::value("Threshold", TaggedValue::F64(50.), false), ], @@ -471,7 +536,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::VibranceNode<_>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Vibrance", TaggedValue::F64(0.), false), ], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], @@ -482,7 +547,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::OpacityNode<_>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Factor", TaggedValue::F64(100.), false), ], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], @@ -493,7 +558,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::PosterizeNode<_>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Value", TaggedValue::F64(4.), false), ], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], @@ -504,7 +569,7 @@ fn static_nodes() -> Vec { category: "Image Adjustments", identifier: NodeImplementation::proto("graphene_core::raster::ExposureNode<_, _, _>"), inputs: vec![ - DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true), + DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Exposure", TaggedValue::F64(0.), false), DocumentInputType::value("Offset", TaggedValue::F64(0.), false), DocumentInputType::value("Gamma Correction", TaggedValue::F64(1.), false), @@ -650,6 +715,7 @@ impl DocumentNodeType { } NodeImplementation::DocumentNode(network) => network.clone(), }; + DocumentNodeImplementation::Network(inner_network) } @@ -663,15 +729,43 @@ impl DocumentNodeType { } } +pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork { + assert_eq!(network.inputs.len(), 1, "Networks wrapped in scope must have exactly one input"); + let input_type = network.nodes[&network.inputs[0]].inputs.iter().find(|&i| matches!(i, NodeInput::Network(_))).unwrap().clone(); + + let inner_network = DocumentNode { + name: "Scope".to_string(), + implementation: DocumentNodeImplementation::Network(network), + inputs: vec![NodeInput::node(0, 1)], + metadata: DocumentNodeMetadata::default(), + }; + // wrap the inner network in a scope + let nodes = vec![ + resolve_document_node_type("Begin Scope") + .expect("Begin Scope node type not found") + .to_document_node(vec![input_type.clone()], DocumentNodeMetadata::default()), + inner_network, + resolve_document_node_type("End Scope") + .expect("End Scope node type not found") + .to_document_node(vec![NodeInput::node(0, 0), NodeInput::node(1, 0)], DocumentNodeMetadata::default()), + ]; + let network = NodeNetwork { + inputs: vec![0], + outputs: vec![NodeOutput::new(2, 0)], + nodes: nodes.into_iter().enumerate().map(|(id, node)| (id as NodeId, node)).collect(), + ..Default::default() + }; + network +} + pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetwork { NodeNetwork { inputs: vec![0], outputs: vec![NodeOutput::new(1, 0)], nodes: [ - resolve_document_node_type("Input").expect("Input node does not exist").to_document_node( - [NodeInput::Network(concrete!(Image)), NodeInput::value(TaggedValue::DAffine2(DAffine2::IDENTITY), false)], - DocumentNodeMetadata::position((8, 4)), - ), + resolve_document_node_type("Input") + .expect("Input node does not exist") + .to_document_node([NodeInput::Network(concrete!(ImageFrame))], DocumentNodeMetadata::position((8, 4))), resolve_document_node_type("Output") .expect("Output node does not exist") .to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))), diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 4b2e4fc7..2bd9161b 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -1,4 +1,5 @@ use crate::messages::frontend::utility_types::FrontendImageData; +use crate::messages::portfolio::document::node_graph::wrap_network_in_scope; use crate::messages::portfolio::document::utility_types::misc::DocumentRenderMode; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; @@ -20,16 +21,18 @@ pub struct NodeGraphExecutor { impl NodeGraphExecutor { /// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`. - fn execute_network(&mut self, mut network: NodeNetwork, image_frame: ImageFrame) -> Result { - network.duplicate_outputs(&mut generate_uuid); - network.remove_dead_nodes(); + fn execute_network(&mut self, network: NodeNetwork, image_frame: ImageFrame) -> Result { + let mut scoped_network = wrap_network_in_scope(network); - debug!("Execute document network:\n{network:#?}"); + scoped_network.duplicate_outputs(&mut generate_uuid); + scoped_network.remove_dead_nodes(); + + debug!("Execute document network:\n{scoped_network:#?}"); // We assume only one output - assert_eq!(network.outputs.len(), 1, "Graph with multiple outputs not yet handled"); + assert_eq!(scoped_network.outputs.len(), 1, "Graph with multiple outputs not yet handled"); let c = Compiler {}; - let proto_network = c.compile_single(network, true)?; + let proto_network = c.compile_single(scoped_network, true)?; debug!("Execute proto network:\n{proto_network}"); assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?"); if let Err(e) = self.executor.update(proto_network) { @@ -74,7 +77,7 @@ impl NodeGraphExecutor { // If the input is just a value, return that value NodeInput::Value { tagged_value, .. } => return dyn_any::downcast::(tagged_value.clone().to_any()).map(|v| *v), // If the input is from a node, set the node to be the output (so that is what is evaluated) - NodeInput::Node { node_id, output_index } => { + NodeInput::Node { node_id, output_index, .. } => { inner_network.outputs[0] = NodeOutput::new(*node_id, *output_index); break 'outer; } diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 4fab245c..13a27612 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -82,6 +82,13 @@ pub mod dynamic { } }*/ +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct SomeNode; +#[node_macro::node_fn(SomeNode)] +fn some(input: T) -> Option { + Some(input) +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct CloneNode(PhantomData); impl<'i, 'n: 'i, O: Clone + 'i> Node<'i, &'n O> for CloneNode { diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index bf3d1cdd..70dea30b 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -43,17 +43,17 @@ pub struct DocumentNode { } impl DocumentNode { - pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize) { + pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, lambda: bool) { let input = self .inputs .iter() .enumerate() .filter(|(_, input)| matches!(input, NodeInput::Network(_))) .nth(offset) - .expect("no network input"); + .unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}")); let index = input.0; - self.inputs[index] = NodeInput::Node { node_id, output_index }; + self.inputs[index] = NodeInput::Node { node_id, output_index, lambda }; } fn resolve_proto_node(mut self) -> ProtoNode { @@ -62,12 +62,12 @@ impl DocumentNode { if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation { let (input, mut args) = match first { NodeInput::Value { tagged_value, .. } => { - assert_eq!(self.inputs.len(), 0); + assert_eq!(self.inputs.len(), 0, "{}, {:?}", &self.name, &self.inputs); (ProtoNodeInput::None, ConstructionArgs::Value(tagged_value)) } - NodeInput::Node { node_id, output_index } => { + NodeInput::Node { node_id, output_index, lambda } => { assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode."); - (ProtoNodeInput::Node(node_id), ConstructionArgs::Nodes(vec![])) + (ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![])) } NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])), }; @@ -81,7 +81,7 @@ impl DocumentNode { if let ConstructionArgs::Nodes(nodes) = &mut args { nodes.extend(self.inputs.iter().map(|input| match input { - NodeInput::Node { node_id, .. } => *node_id, + NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda), _ => unreachable!(), })); } @@ -103,11 +103,15 @@ impl DocumentNode { P: Fn(String, usize) -> Option, { for (index, input) in self.inputs.iter_mut().enumerate() { - let &mut NodeInput::Node{node_id: id, output_index} = input else { + let &mut NodeInput::Node{node_id: id, output_index, lambda} = input else { continue; }; if let Some(&new_id) = new_ids.get(&id) { - *input = NodeInput::Node { node_id: new_id, output_index }; + *input = NodeInput::Node { + node_id: new_id, + output_index, + lambda, + }; } else if let Some(new_input) = default_input(self.name.clone(), index) { *input = new_input; } else { @@ -121,21 +125,28 @@ impl DocumentNode { #[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 }, + Node { node_id: NodeId, output_index: usize, lambda: bool }, Value { tagged_value: value::TaggedValue, exposed: bool }, Network(Type), } impl NodeInput { pub const fn node(node_id: NodeId, output_index: usize) -> Self { - Self::Node { node_id, output_index } + Self::Node { node_id, output_index, lambda: false } + } + pub const fn lambda(node_id: NodeId, output_index: usize) -> Self { + Self::Node { node_id, output_index, lambda: true } } pub const fn value(tagged_value: value::TaggedValue, exposed: bool) -> Self { Self::Value { tagged_value, exposed } } fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) { - if let &mut NodeInput::Node { node_id, output_index } = self { - *self = NodeInput::Node { node_id: f(node_id), output_index } + if let &mut NodeInput::Node { node_id, output_index, lambda } = self { + *self = NodeInput::Node { + node_id: f(node_id), + output_index, + lambda, + } } } pub fn is_exposed(&self) -> bool { @@ -246,7 +257,7 @@ impl NodeNetwork { } for input in &mut node.inputs { - let &mut NodeInput::Node { node_id, output_index} = input else { + let &mut NodeInput::Node { node_id, output_index, .. } = input else { continue; }; // Use the initial node when getting the first output @@ -362,9 +373,9 @@ impl NodeNetwork { for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) { let offset = network_offsets.entry(network_input).or_insert(0); match document_input { - NodeInput::Node { node_id, output_index } => { + NodeInput::Node { node_id, output_index, lambda } => { let network_input = self.nodes.get_mut(network_input).unwrap(); - network_input.populate_first_network_input(node_id, output_index, *offset); + network_input.populate_first_network_input(node_id, output_index, *offset, lambda); } NodeInput::Value { tagged_value, exposed } => { // Skip formatting very large values for seconds in performance speedup @@ -386,7 +397,7 @@ impl NodeNetwork { assert!(!self.nodes.contains_key(&new_id)); self.nodes.insert(new_id, value_node); let network_input = self.nodes.get_mut(network_input).unwrap(); - network_input.populate_first_network_input(new_id, 0, *offset); + network_input.populate_first_network_input(new_id, 0, *offset, false); } NodeInput::Network(_) => { *network_offsets.get_mut(network_input).unwrap() += 1; @@ -403,6 +414,7 @@ impl NodeNetwork { .map(|&NodeOutput { node_id, node_output_index }| NodeInput::Node { node_id, output_index: node_output_index, + lambda: false, }) .collect(); @@ -660,7 +672,7 @@ mod test { let reference = ProtoNode { identifier: "graphene_core::structural::ConsNode".into(), input: ProtoNodeInput::Network(concrete!(u32)), - construction_args: ConstructionArgs::Nodes(vec![0]), + construction_args: ConstructionArgs::Nodes(vec![(0, false)]), }; assert_eq!(proto_node, reference); } @@ -675,7 +687,7 @@ mod test { 1, ProtoNode { identifier: "graphene_core::ops::IdNode".into(), - input: ProtoNodeInput::Node(11), + input: ProtoNodeInput::Node(11, false), construction_args: ConstructionArgs::Nodes(vec![]), }, ), @@ -684,14 +696,14 @@ mod test { ProtoNode { identifier: "graphene_core::structural::ConsNode".into(), input: ProtoNodeInput::Network(concrete!(u32)), - construction_args: ConstructionArgs::Nodes(vec![14]), + construction_args: ConstructionArgs::Nodes(vec![(14, false)]), }, ), ( 11, ProtoNode { identifier: "graphene_core::ops::AddNode".into(), - input: ProtoNodeInput::Node(10), + input: ProtoNodeInput::Node(10, false), construction_args: ConstructionArgs::Nodes(vec![]), }, ), diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 3fcf3a4d..c2e9d9c4 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -43,7 +43,7 @@ impl core::fmt::Display for ProtoNetwork { match &node.input { ProtoNodeInput::None => f.write_str("None")?, ProtoNodeInput::Network(ty) => f.write_fmt(format_args!("Network (type = {:?})", ty))?, - ProtoNodeInput::Node(_) => f.write_str("Node")?, + ProtoNodeInput::Node(_, _) => f.write_str("Node")?, } f.write_str("\n")?; @@ -54,7 +54,7 @@ impl core::fmt::Display for ProtoNetwork { } ConstructionArgs::Nodes(nodes) => { for id in nodes { - write_node(f, network, *id, indent + 1)?; + write_node(f, network, id.0, indent + 1)?; } } } @@ -71,7 +71,8 @@ impl core::fmt::Display for ProtoNetwork { #[derive(Debug, Clone)] pub enum ConstructionArgs { Value(value::TaggedValue), - Nodes(Vec), + // the bool indicates whether to treat the node as lambda node + Nodes(Vec<(NodeId, bool)>), } impl PartialEq for ConstructionArgs { @@ -101,7 +102,7 @@ impl Hash for ConstructionArgs { impl ConstructionArgs { pub fn new_function_args(&self) -> Vec { match self { - ConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!("n{}", n)).collect(), + ConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!("n{}", n.0)).collect(), ConstructionArgs::Value(value) => vec![format!("{:?}", value)], } } @@ -118,13 +119,14 @@ pub struct ProtoNode { pub enum ProtoNodeInput { None, Network(Type), - Node(NodeId), + // the bool indicates whether to treat the node as lambda node + Node(NodeId, bool), } impl ProtoNodeInput { pub fn unwrap_node(self) -> NodeId { match self { - ProtoNodeInput::Node(id) => id, + ProtoNodeInput::Node(id, _) => id, _ => panic!("tried to unwrap id from non node input \n node: {:#?}", self), } } @@ -142,7 +144,7 @@ impl ProtoNode { "network".hash(&mut hasher); ty.hash(&mut hasher); } - ProtoNodeInput::Node(id) => id.hash(&mut hasher), + ProtoNodeInput::Node(id, lambda) => (id, lambda).hash(&mut hasher), }; Some(hasher.finish() as NodeId) } @@ -155,16 +157,18 @@ impl ProtoNode { } } - pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) { - if let ProtoNodeInput::Node(id) = self.input { - self.input = ProtoNodeInput::Node(f(id)) + pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId, skip_lambdas: bool) { + if let ProtoNodeInput::Node(id, lambda) = self.input { + if !(skip_lambdas && lambda) { + self.input = ProtoNodeInput::Node(f(id), lambda) + } } if let ConstructionArgs::Nodes(ids) = &mut self.construction_args { - ids.iter_mut().for_each(|id| *id = f(*id)); + ids.iter_mut().filter(|(_, lambda)| !(skip_lambdas && *lambda)).for_each(|(id, _)| *id = f(*id)); } } - pub fn unwrap_construction_nodes(&self) -> Vec { + pub fn unwrap_construction_nodes(&self) -> Vec<(NodeId, bool)> { match &self.construction_args { ConstructionArgs::Nodes(nodes) => nodes.clone(), _ => panic!("tried to unwrap nodes from non node construction args \n node: {:#?}", self), @@ -186,12 +190,12 @@ impl ProtoNetwork { pub fn collect_outwards_edges(&self) -> HashMap> { let mut edges: HashMap> = HashMap::new(); for (id, node) in &self.nodes { - if let ProtoNodeInput::Node(ref_id) = &node.input { + if let ProtoNodeInput::Node(ref_id, _) = &node.input { self.check_ref(ref_id, id); edges.entry(*ref_id).or_default().push(*id) } if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args { - for ref_id in ref_nodes { + for (ref_id, _) in ref_nodes { self.check_ref(ref_id, id); edges.entry(*ref_id).or_default().push(*id) } @@ -210,7 +214,7 @@ impl ProtoNetwork { let mut lookup = self.nodes.iter().map(|(id, _)| (*id, *id)).collect::>(); if let Some(sni) = self.nodes[index].1.stable_node_id() { lookup.insert(self.nodes[index].0, sni); - self.replace_node_references(&lookup); + self.replace_node_references(&lookup, false); self.nodes[index].0 = sni; sni } else { @@ -221,12 +225,12 @@ impl ProtoNetwork { pub fn collect_inwards_edges(&self) -> HashMap> { let mut edges: HashMap> = HashMap::new(); for (id, node) in &self.nodes { - if let ProtoNodeInput::Node(ref_id) = &node.input { + if let ProtoNodeInput::Node(ref_id, _) = &node.input { self.check_ref(ref_id, id); edges.entry(*id).or_default().push(*ref_id) } if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args { - for ref_id in ref_nodes { + for (ref_id, _) in ref_nodes { self.check_ref(ref_id, id); edges.entry(*id).or_default().push(*ref_id) } @@ -248,21 +252,22 @@ impl ProtoNetwork { 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 { + if let ProtoNodeInput::Node(input_node, false) = node.input { 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.clone())) } else { + resolved.insert(*id); None } }) { lookup.insert(id, compose_node_id); - self.replace_node_references(&lookup); + self.replace_node_references(&lookup, true); self.nodes.push(( compose_node_id, ProtoNode { identifier: NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), - construction_args: ConstructionArgs::Nodes(vec![input_node, id]), + construction_args: ConstructionArgs::Nodes(vec![(input_node, false), (id, true)]), input, }, )); @@ -338,13 +343,13 @@ impl ProtoNetwork { (pos as NodeId, node) }) .collect(); - self.replace_node_references(&lookup); + self.replace_node_references(&lookup, false); assert_eq!(order.len(), self.nodes.len()); } - fn replace_node_references(&mut self, lookup: &HashMap) { + fn replace_node_references(&mut self, lookup: &HashMap, skip_lambdas: bool) { self.nodes.iter_mut().for_each(|(_, node)| { - node.map_ids(|id| *lookup.get(&id).expect("node not found in lookup table")); + node.map_ids(|id| *lookup.get(&id).expect("node not found in lookup table"), skip_lambdas); }); self.inputs = self.inputs.iter().filter_map(|id| lookup.get(id).copied()).collect(); self.output = *lookup.get(&self.output).unwrap(); @@ -403,7 +408,7 @@ impl TypingContext { // If the node has nodes as parameters we can infer the types from the node outputs ConstructionArgs::Nodes(ref nodes) => nodes .iter() - .map(|id| { + .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")) @@ -416,7 +421,7 @@ impl TypingContext { let input = match node.input { ProtoNodeInput::None => concrete!(()), ProtoNodeInput::Network(ref ty) => ty.clone(), - ProtoNodeInput::Node(id) => { + ProtoNodeInput::Node(id, _) => { let input = self .inferred .get(&id) @@ -573,7 +578,7 @@ mod test { println!("{:#?}", construction_network); assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value"); assert_eq!(construction_network.nodes.len(), 6); - assert_eq!(construction_network.nodes[5].1.construction_args, ConstructionArgs::Nodes(vec![3, 4])); + assert_eq!(construction_network.nodes[5].1.construction_args, ConstructionArgs::Nodes(vec![(3, false), (4, true)])); } #[test] @@ -589,11 +594,11 @@ mod test { ids, vec![ 15907139529964845467, - 14192092348022507362, - 14714934190542167928, - 4518275895314664278, - 13912679582583718470, - 3236993912700824422 + 1552706903207877482, + 15211082859148708110, + 3361684226823984981, + 16609475913638361514, + 5640155373642511298 ] ); } @@ -607,7 +612,7 @@ mod test { 7, ProtoNode { identifier: "id".into(), - input: ProtoNodeInput::Node(11), + input: ProtoNodeInput::Node(11, false), construction_args: ConstructionArgs::Nodes(vec![]), }, ), @@ -615,7 +620,7 @@ mod test { 1, ProtoNode { identifier: "id".into(), - input: ProtoNodeInput::Node(11), + input: ProtoNodeInput::Node(11, false), construction_args: ConstructionArgs::Nodes(vec![]), }, ), @@ -624,14 +629,14 @@ mod test { ProtoNode { identifier: "cons".into(), input: ProtoNodeInput::Network(concrete!(u32)), - construction_args: ConstructionArgs::Nodes(vec![14]), + construction_args: ConstructionArgs::Nodes(vec![(14, false)]), }, ), ( 11, ProtoNode { identifier: "add".into(), - input: ProtoNodeInput::Node(10), + input: ProtoNodeInput::Node(10, false), construction_args: ConstructionArgs::Nodes(vec![]), }, ), diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 89ee7efd..0f7558b0 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -40,6 +40,28 @@ impl<_I, _O, S0> DynAnyRefNode<_I, _O, S0> { Self { node, _i: core::marker::PhantomData } } } +pub struct DynAnyInRefNode { + node: Node, + _i: PhantomData<(I, O)>, +} +impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> Node<'input, Any<'input>> for DynAnyInRefNode<_I, _O, N> +where + N: for<'any_input> Node<'any_input, &'any_input _I, Output = _O>, +{ + type Output = Any<'input>; + fn eval<'node: 'input>(&'node self, input: Any<'input>) -> Self::Output { + { + let node_name = core::any::type_name::(); + let input: Box<&_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {e} in:\n{node_name}")); + Box::new(self.node.eval(*input)) + } + } +} +impl<_I, _O, S0> DynAnyInRefNode<_I, _O, S0> { + pub const fn new(node: S0) -> Self { + Self { node, _i: core::marker::PhantomData } + } +} pub trait IntoTypeErasedNode<'n> { fn into_type_erased(self) -> TypeErasedPinned<'n>; diff --git a/node-graph/gstd/src/memo.rs b/node-graph/gstd/src/memo.rs index 0fbbc1ac..d31c92e8 100644 --- a/node-graph/gstd/src/memo.rs +++ b/node-graph/gstd/src/memo.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use graphene_core::Node; use once_cell::sync::OnceCell; @@ -21,3 +23,71 @@ impl CacheNode { CacheNode { cache: OnceCell::new() } } } + +/// Caches the output of a given Node and acts as a proxy +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct LetNode { + cache: OnceCell, +} +impl<'i, T: 'i> Node<'i, Option> for LetNode { + type Output = &'i T; + fn eval<'s: 'i>(&'s self, input: Option) -> Self::Output { + match input { + Some(input) => { + self.cache.set(input).unwrap_or_else(|_| error!("Let node was set twice but is not mutable")); + self.cache.get().unwrap() + } + None => self.cache.get().expect("Let node was not initialized"), + } + } +} + +impl LetNode { + pub const fn new() -> LetNode { + LetNode { cache: OnceCell::new() } + } +} + +/// Caches the output of a given Node and acts as a proxy +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct EndLetNode { + input: Input, +} +impl<'i, T: 'i, Input> Node<'i, &'i T> for EndLetNode +where + Input: Node<'i, ()>, +{ + type Output = ::Output; + fn eval<'s: 'i>(&'s self, _: &'i T) -> Self::Output { + self.input.eval(()) + } +} + +impl EndLetNode { + pub const fn new(input: Input) -> EndLetNode { + EndLetNode { input } + } +} + +pub use graphene_core::ops::SomeNode as InitNode; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct RefNode { + let_node: Let, + _t: PhantomData, +} +impl<'i, T: 'i, Let> Node<'i, ()> for RefNode +where + Let: for<'a> Node<'a, Option, Output = &'a T>, +{ + type Output = &'i T; + fn eval<'s: 'i>(&'s self, _: ()) -> Self::Output { + self.let_node.eval(None) + } +} + +impl RefNode { + pub const fn new(let_node: Let) -> RefNode { + RefNode { let_node, _t: PhantomData } + } +} diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index ce3d238d..4ccd99c0 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -131,13 +131,14 @@ pub struct BlendImageNode { map_fn: MapFn, } +// TODO: Implement proper blending #[node_macro::node_fn(BlendImageNode)] -fn blend_image(image: Image, second: Image, map_fn: &'any_input MapFn) -> Image +fn blend_image(image: ImageFrame, second: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame where MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input, { let mut image = image; - for (pixel, sec_pixel) in &mut image.data.iter_mut().zip(second.data.iter()) { + for (pixel, sec_pixel) in &mut image.image.data.iter_mut().zip(second.image.data.iter()) { *pixel = map_fn.eval((*pixel, *sec_pixel)); } image diff --git a/node-graph/interpreted-executor/src/executor.rs b/node-graph/interpreted-executor/src/executor.rs index 31a6624c..3402c2e5 100644 --- a/node-graph/interpreted-executor/src/executor.rs +++ b/node-graph/interpreted-executor/src/executor.rs @@ -152,6 +152,7 @@ impl BorrowTree { self.store_node(Arc::new(node), id); } ConstructionArgs::Nodes(ids) => { + let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect(); let construction_nodes = self.node_refs(&ids); let constructor = typing_context.constructor(id).ok_or(format!("No constructor found for node {:?}", identifier))?; let node = constructor(construction_nodes); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index ee418663..1e3e519d 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -10,14 +10,14 @@ use graphene_core::structural::Then; use graphene_core::value::{ClonedNode, ForgetNode, ValueNode}; use graphene_core::{Node, NodeIO, NodeIOTypes}; -use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DowncastBothRefNode, DynAnyNode, IntoTypeErasedNode, TypeErasedPinnedRef}; +use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DowncastBothRefNode, DynAnyInRefNode, DynAnyNode, DynAnyRefNode, IntoTypeErasedNode, TypeErasedPinnedRef}; use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor}; use graph_craft::proto::NodeConstructor; use graphene_core::{concrete, generic}; -use graphene_std::memo::CacheNode; +use graphene_std::memo::{CacheNode, LetNode}; use crate::executor::NodeContainer; @@ -122,7 +122,7 @@ fn node_registry() -> HashMap, 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::CloneNode<_>, input: &Image, params: []), + register_node!(graphene_core::ops::CloneNode<_>, input: &ImageFrame, 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]), @@ -131,6 +131,7 @@ fn node_registry() -> HashMap, 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::SomeNode, input: ImageFrame, params: []), vec![( NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"), |args| { @@ -146,19 +147,19 @@ fn node_registry() -> HashMap"), |args| { use graphene_core::Node; - let image: DowncastBothNode<(), Image> = DowncastBothNode::new(args[0]); + let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]); let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(blend_mode.eval(())), ClonedNode::new(opacity.eval(()))); let node = graphene_std::raster::BlendImageNode::new(image, ValueNode::new(blend_node)); - let _ = &node as &dyn for<'i> Node<'i, Image, Output = Image>; - let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + let _ = &node as &dyn for<'i> Node<'i, ImageFrame, Output = ImageFrame>; + 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![(concrete!(()), concrete!(Image)), (concrete!(()), concrete!(BlendMode)), (concrete!(()), concrete!(f64))], + concrete!(ImageFrame), + concrete!(ImageFrame), + vec![(concrete!(()), concrete!(ImageFrame)), (concrete!(()), concrete!(BlendMode)), (concrete!(()), concrete!(f64))], ), )], raster_node!(graphene_core::raster::GrayscaleNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]), @@ -171,6 +172,35 @@ fn node_registry() -> HashMap, params: [f64]), raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f64, f64, f64]), vec![ + ( + NodeIdentifier::new("graphene_std::memo::LetNode<_>"), + |_| { + let node: LetNode = graphene_std::memo::LetNode::new(); + let any = graphene_std::any::DynAnyRefNode::new(node); + any.into_type_erased() + }, + NodeIOTypes::new(concrete!(Option), concrete!(&ImageFrame), vec![]), + ), + ( + NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"), + |args| { + let input: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); + let node = graphene_std::memo::EndLetNode::new(input); + let any: DynAnyInRefNode = graphene_std::any::DynAnyInRefNode::new(node); + any.into_type_erased() + }, + NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![(concrete!(()), concrete!(ImageFrame))]), + ), + ( + NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"), + |args| { + let map_fn: DowncastBothRefNode, ImageFrame> = DowncastBothRefNode::new(args[0]); + let node = graphene_std::memo::RefNode::new(map_fn); + let any = graphene_std::any::DynAnyRefNode::new(node); + any.into_type_erased() + }, + NodeIOTypes::new(concrete!(()), concrete!(&ImageFrame), vec![]), + ), ( NodeIdentifier::new("graphene_core::structural::MapImageNode"), |args| { @@ -257,7 +287,7 @@ fn node_registry() -> HashMap = graphene_std::memo::CacheNode::new(); - let any = graphene_std::any::DynAnyRefNode::new(node); + let any = DynAnyRefNode::new(node); any.into_type_erased() }, NodeIOTypes::new(concrete!(Image), concrete!(&Image), vec![]),