From 1398405529b31c41e7dc478850d73ace2e0531cb Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 6 Jul 2025 14:04:44 -0700 Subject: [PATCH] merge onto master --- editor/src/dispatcher.rs | 47 +- .../animation/animation_message_handler.rs | 6 +- .../export_dialog_message_handler.rs | 2 +- .../new_document_dialog_message_handler.rs | 4 +- .../preferences_dialog_message_handler.rs | 2 +- .../src/messages/frontend/frontend_message.rs | 12 +- .../input_preprocessor_message_handler.rs | 17 +- editor/src/messages/message.rs | 22 +- .../portfolio/document/document_message.rs | 5 +- .../document/document_message_handler.rs | 53 +- .../graph_operation_message_handler.rs | 14 +- .../document/graph_operation/utility_types.rs | 6 +- .../node_graph/document_node_definitions.rs | 1 - .../document/node_graph/node_graph_message.rs | 6 - .../node_graph/node_graph_message_handler.rs | 48 +- .../properties_panel_message_handler.rs | 21 +- .../utility_types/network_interface.rs | 309 ++- .../portfolio/document/utility_types/nodes.rs | 3 +- .../messages/portfolio/portfolio_message.rs | 35 +- .../portfolio/portfolio_message_handler.rs | 550 ++++- .../spreadsheet/spreadsheet_message.rs | 31 +- .../spreadsheet_message_handler.rs | 84 +- .../src/messages/portfolio/utility_types.rs | 3 +- .../preferences_message_handler.rs | 5 +- .../shape_gizmos/number_of_points_dial.rs | 2 +- .../shape_gizmos/point_radius_handle.rs | 2 +- .../graph_modification_utils.rs | 5 +- .../common_functionality/shapes/line_shape.rs | 2 +- .../messages/tool/tool_messages/brush_tool.rs | 4 +- .../tool/tool_messages/freehand_tool.rs | 2 +- .../messages/tool/tool_messages/pen_tool.rs | 6 +- .../tool/tool_messages/select_tool.rs | 4 +- .../messages/tool/tool_messages/shape_tool.rs | 6 +- .../tool/tool_messages/spline_tool.rs | 2 +- .../messages/tool/tool_messages/text_tool.rs | 14 +- .../transform_layer_message_handler.rs | 2 +- editor/src/node_graph_executor.rs | 638 +++--- editor/src/node_graph_executor/runtime.rs | 437 +--- frontend/src/messages.ts | 8 +- frontend/src/state-providers/node-graph.ts | 11 +- node-graph/gcore/src/context.rs | 1 + node-graph/gcore/src/lib.rs | 14 +- node-graph/gcore/src/memo.rs | 68 +- node-graph/gcore/src/ops.rs | 4 - node-graph/gcore/src/registry.rs | 23 +- node-graph/gcore/src/structural.rs | 2 +- node-graph/gcore/src/uuid.rs | 9 + node-graph/graph-craft/src/document.rs | 1974 ++++++++--------- node-graph/graph-craft/src/document/value.rs | 22 + .../graph-craft/src/graphene_compiler.rs | 35 - node-graph/graph-craft/src/proto.rs | 967 +++----- node-graph/graph-craft/src/util.rs | 6 - node-graph/graphene-cli/src/main.rs | 8 +- .../benches/benchmark_util.rs | 8 +- .../benches/run_demo_art_criterion.rs | 8 +- .../src/dynamic_executor.rs | 421 ++-- node-graph/interpreted-executor/src/lib.rs | 4 +- .../interpreted-executor/src/node_registry.rs | 63 +- node-graph/interpreted-executor/src/util.rs | 21 +- node-graph/preprocessor/src/lib.rs | 1 + 60 files changed, 2861 insertions(+), 3229 deletions(-) diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index ee098193c5..0349d5ad55 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -5,7 +5,8 @@ use crate::messages::prelude::*; #[derive(Debug, Default)] pub struct Dispatcher { - buffered_queue: Option>>, + buffered_queue: Vec, + queueing_messages: bool, message_queues: Vec>, pub responses: Vec, pub message_handlers: DispatcherMessageHandlers, @@ -90,11 +91,10 @@ impl Dispatcher { pub fn handle_message>(&mut self, message: T, process_after_all_current: bool) { let message = message.into(); - // Add all additional messages to the buffer if it exists (except from the end buffer message) - if !matches!(message, Message::EndBuffer { .. }) { - if let Some(buffered_queue) = &mut self.buffered_queue { - Self::schedule_execution(buffered_queue, true, [message]); - + // Add all additional messages to the queue if it exists (except from the end queue message) + if !matches!(message, Message::EndQueue) { + if self.queueing_messages { + self.buffered_queue.push(message); return; } } @@ -126,6 +126,41 @@ impl Dispatcher { // Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend match message { + Message::StartQueue => { + self.queueing_messages = true; + } + Message::EndQueue => { + self.queueing_messages = false; + } + Message::ProcessQueue((render_output_metadata, introspected_inputs)) => { + let message = PortfolioMessage::ProcessEvaluationResponse { + evaluation_metadata: render_output_metadata, + introspected_inputs, + }; + // Add the message to update the state with the render output + Self::schedule_execution(&mut self.message_queues, true, [message]); + + // Schedule all queued messages to be run (in the order they were added) + Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.buffered_queue)); + } + Message::NoOp => {} + Message::Init => { + // Load persistent data from the browser database + queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument); + queue.add(FrontendMessage::TriggerLoadPreferences); + + // Display the menu bar at the top of the window + queue.add(MenuBarMessage::SendLayout); + + // Send the information for tooltips and categories for each node/input. + queue.add(FrontendMessage::SendUIMetadata { + node_descriptions: document_node_definitions::collect_node_descriptions(), + node_types: document_node_definitions::collect_node_types(), + }); + + // Finish loading persistent data from the browser database + queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments); + } Message::Animation(message) => { self.message_handlers.animation_message_handler.process_message(message, &mut queue, ()); } diff --git a/editor/src/messages/animation/animation_message_handler.rs b/editor/src/messages/animation/animation_message_handler.rs index 32a7979ab2..afb9717a10 100644 --- a/editor/src/messages/animation/animation_message_handler.rs +++ b/editor/src/messages/animation/animation_message_handler.rs @@ -84,7 +84,7 @@ impl MessageHandler for AnimationMessageHandler { } AnimationMessage::SetFrameIndex { frame } => { self.frame_index = frame; - responses.add(PortfolioMessage::SubmitActiveGraphRender); + responses.add(PortfolioMessage::EvaluateActiveDocument); // Update the restart and pause/play buttons responses.add(PortfolioMessage::UpdateDocumentWidgets); } @@ -100,7 +100,7 @@ impl MessageHandler for AnimationMessageHandler { } AnimationMessage::UpdateTime => { if self.is_playing() { - responses.add(PortfolioMessage::SubmitActiveGraphRender); + responses.add(PortfolioMessage::EvaluateActiveDocument); if self.live_preview_recently_zero { // Update the restart and pause/play buttons @@ -116,7 +116,7 @@ impl MessageHandler for AnimationMessageHandler { _ => AnimationState::Stopped, }; self.live_preview_recently_zero = true; - responses.add(PortfolioMessage::SubmitActiveGraphRender); + responses.add(PortfolioMessage::EvaluateActiveDocument); // Update the restart and pause/play buttons responses.add(PortfolioMessage::UpdateDocumentWidgets); } diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs index 980f3e3e25..91b80de3e8 100644 --- a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs @@ -43,7 +43,7 @@ impl MessageHandler> for Exp ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background, ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area, - ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport { + ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::ActiveDocumentExport { file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(), file_type: self.file_type, scale_factor: self.scale_factor, diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index 6121424de3..1162f2f100 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -24,7 +24,7 @@ impl MessageHandler for NewDocumentDialogMessageHa let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0; if create_artboard { - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); responses.add(GraphOperationMessage::NewArtboard { id: NodeId::new(), artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()), @@ -33,7 +33,7 @@ impl MessageHandler for NewDocumentDialogMessageHa // TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead // Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); responses.add(DocumentMessage::DeselectAllLayers); } diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs index 66173d8898..5c38472324 100644 --- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs +++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs @@ -174,7 +174,7 @@ impl PreferencesDialogMessageHandler { let use_vello = vec![ Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(), - CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu()) + CheckboxInput::new(preferences.use_vello()) .tooltip(vello_tooltip) .disabled(!preferences.supports_wgpu()) .on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into()) diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index c24ebc405c..c90f861224 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -14,6 +14,9 @@ use graphene_std::text::Font; #[impl_message(Message, Frontend)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] pub enum FrontendMessage { + ClearNodeThumbnail { + sni: NodeId, + }, // Display prefix: make the frontend show something, like a dialog DisplayDialog { title: String, @@ -272,10 +275,6 @@ pub enum FrontendMessage { UpdateNodeGraphTransform { transform: Transform, }, - UpdateNodeThumbnail { - id: NodeId, - value: String, - }, UpdateOpenDocumentsList { #[serde(rename = "openDocuments")] open_documents: Vec, @@ -285,6 +284,11 @@ pub enum FrontendMessage { layout_target: LayoutTarget, diff: Vec, }, + UpdateThumbnails { + add: Vec<(NodeId, String)>, + clear: Vec, + // remove: Vec, + }, UpdateToolOptionsLayout { #[serde(rename = "layoutTarget")] layout_target: LayoutTarget, diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index 16f9dca9cf..16d4c5c967 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -14,7 +14,7 @@ pub struct InputPreprocessorMessageContext { #[derive(Debug, Default, ExtractField)] pub struct InputPreprocessorMessageHandler { pub frame_time: FrameTimeInfo, - pub time: u64, + pub time: f64, pub keyboard: KeyStates, pub mouse: MouseState, pub viewport_bounds: ViewportBounds, @@ -98,9 +98,7 @@ impl MessageHandler f self.translate_mouse_event(mouse_state, false, responses); } InputPreprocessorMessage::CurrentTime { timestamp } => { - responses.add(AnimationMessage::SetTime { time: timestamp as f64 }); - self.time = timestamp; - self.frame_time.advance_timestamp(Duration::from_millis(timestamp)); + self.time = timestamp as f64; } InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); @@ -187,10 +185,19 @@ impl InputPreprocessorMessageHandler { } } - pub fn document_bounds(&self) -> [DVec2; 2] { + pub fn viewport_bounds(&self) -> [DVec2; 2] { // IPP bounds are relative to the entire application [(0., 0.).into(), self.viewport_bounds.bottom_right - self.viewport_bounds.top_left] } + + pub fn document_bounds(&self, document_to_viewport: DAffine2) -> [DVec2; 2] { + // IPP bounds are relative to the entire application + let mut bounds = self.viewport_bounds(); + for point in &mut bounds { + *point = document_to_viewport.transform_point2(*point); + } + bounds + } } #[cfg(test)] diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index 18023c1b6c..c1cb9a4ba7 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -1,11 +1,27 @@ +use std::sync::Arc; + use crate::messages::prelude::*; -use graphene_std::renderer::RenderMetadata; +use graphene_std::{IntrospectMode, uuid::CompiledProtonodeInput}; use graphite_proc_macros::*; #[impl_message] -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub enum Message { - // Sub-messages + NoOp, + Init, + Batched(Box<[Message]>), + // Adds any subsequent messages to the queue + StartQueue, + // Stop adding messages to the queue. + EndQueue, + // Processes all messages that are queued, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete + ProcessQueue( + ( + graphene_std::renderer::RenderMetadata, + Vec<(CompiledProtonodeInput, IntrospectMode, Box)>, + ), + ), + #[child] Animation(AnimationMessage), #[child] diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index ae3576d2a1..3cd6bdeaf4 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -7,12 +7,13 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, use crate::messages::portfolio::utility_types::PanelType; use crate::messages::prelude::*; use glam::DAffine2; -use graph_craft::document::NodeId; +use graphene_std::uuid::CompiledProtonodeInput; use graphene_std::Color; use graphene_std::raster::BlendMode; use graphene_std::raster::Image; +use graphene_std::renderer::ClickTarget; use graphene_std::transform::Footprint; -use graphene_std::vector::click_target::ClickTarget; +use graphene_std::uuid::NodeId; use graphene_std::vector::style::ViewMode; #[impl_message(Message, PortfolioMessage, Document)] diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 91303f8d97..ffe5c6411f 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -33,9 +33,11 @@ use graphene_std::math::quad::Quad; use graphene_std::path_bool::{boolean_intersect, path_bool_lib}; use graphene_std::raster::BlendMode; use graphene_std::raster_types::{Raster, RasterDataTable}; +use graphene_std::uuid::NodeId; use graphene_std::vector::PointId; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::style::ViewMode; +use std::sync::Arc; use std::time::Duration; #[derive(ExtractField)] @@ -43,10 +45,11 @@ pub struct DocumentMessageContext<'a> { pub document_id: DocumentId, pub ipp: &'a InputPreprocessorMessageHandler, pub persistent_data: &'a PersistentData, - pub executor: &'a mut NodeGraphExecutor, pub current_tool: &'a ToolType, pub preferences: &'a PreferencesMessageHandler, pub device_pixel_ratio: f64, + // pub introspected_inputs: &HashMap>, + // pub downcasted_inputs: &mut HashMap, } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)] @@ -104,10 +107,10 @@ pub struct DocumentMessageHandler { // /// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks #[serde(skip)] - breadcrumb_network_path: Vec, + pub breadcrumb_network_path: Vec, /// Path to network that is currently selected. Updated based on the most recently clicked panel. #[serde(skip)] - selection_network_path: Vec, + pub selection_network_path: Vec, /// Stack of document network snapshots for previous history states. #[serde(skip)] document_undo_history: VecDeque, @@ -176,11 +179,12 @@ impl MessageHandler> for DocumentMes document_id, ipp, persistent_data, - executor, current_tool, preferences, device_pixel_ratio, - } = context; + // introspected_inputs, + // downcasted_inputs + } = data; let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path); let selected_visible_layers_bounding_box_viewport = self.selected_visible_layers_bounding_box_viewport(); @@ -342,7 +346,7 @@ impl MessageHandler> for DocumentMes node_ids: vec![node_id], delete_children: true, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SendGraph); responses.add(DocumentMessage::EndTransaction); @@ -441,7 +445,7 @@ impl MessageHandler> for DocumentMes } let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect(); responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } DocumentMessage::EnterNestedNetwork { node_id } => { self.breadcrumb_network_path.push(node_id); @@ -713,7 +717,7 @@ impl MessageHandler> for DocumentMes } } - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SendGraph); } DocumentMessage::MoveSelectedLayersToGroup { parent } => { @@ -729,7 +733,7 @@ impl MessageHandler> for DocumentMes } responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![parent.to_node()] }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(DocumentMessage::DocumentStructureChanged); responses.add(NodeGraphMessage::SendGraph); } @@ -1152,7 +1156,7 @@ impl MessageHandler> for DocumentMes DocumentMessage::SetNodePinned { node_id, pinned } => { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::SetPinned { node_id, pinned }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SendGraph); } @@ -1215,7 +1219,7 @@ impl MessageHandler> for DocumentMes } DocumentMessage::SetViewMode { view_mode } => { self.view_mode = view_mode; - responses.add_front(NodeGraphMessage::RunDocumentGraph); + responses.add_front(PortfolioMessage::CompileActiveDocument); } DocumentMessage::AddTransaction => { // Reverse order since they are added to the front @@ -1306,6 +1310,17 @@ impl MessageHandler> for DocumentMes self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled; responses.add(PortfolioMessage::UpdateDocumentWidgets); } + // DocumentMessage::ToggleAnimation => match self.animation_state { + // AnimationState::Stopped => {self.animation_state = AnimationState::Playing { start: ipp.time }; responses.add(PortfolioMessage::EvaluateActiveDocument)}, + // AnimationState::Playing { start } => self.animation_state = AnimationState::Paused { start , pause_time: ipp.time }, + // AnimationState::Paused { start, .. } => {self.animation_state = AnimationState::Playing { start }; responses.add(PortfolioMessage::EvaluateActiveDocument)}, + // }, + // DocumentMessage::RestartAnimation => { + // self.animation_state = match self.animation_state { + // AnimationState::Playing { .. } => AnimationState::Playing { start: ipp.time }, + // _ => AnimationState::Stopped, + // }; + // } DocumentMessage::UpdateUpstreamTransforms { upstream_footprints, local_transforms, @@ -1364,7 +1379,7 @@ impl MessageHandler> for DocumentMes responses.add(DocumentMessage::UngroupLayer { layer: folder }); } - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(DocumentMessage::DocumentStructureChanged); responses.add(NodeGraphMessage::SendGraph); } @@ -1402,7 +1417,7 @@ impl MessageHandler> for DocumentMes node_ids: vec![layer.to_node()], delete_children: true, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SendGraph); } @@ -1417,7 +1432,7 @@ impl MessageHandler> for DocumentMes center: Key::Alt, duplicate: Key::Alt, })); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } else { let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else { return; @@ -1901,11 +1916,11 @@ impl DocumentMessageHandler { // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(NodeGraphMessage::SelectedNodesUpdated); - responses.add(NodeGraphMessage::ForceRunDocumentGraph); // TODO: Remove once the footprint is used to load the imports/export distances from the edge responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::SetGridAlignedEdges); - responses.add(Message::StartBuffer); + responses.add(PortfolioMessage::CompileActiveDocument); + responses.add(Message::StartQueue); Some(previous_network) } pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { @@ -1934,7 +1949,7 @@ impl DocumentMessageHandler { // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(NodeGraphMessage::SelectedNodesUpdated); - responses.add(NodeGraphMessage::ForceRunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::SendWires); Some(previous_network) @@ -2062,7 +2077,7 @@ impl DocumentMessageHandler { if let (Some(upstream_boolean_op), Some(only_selected_layer)) = (upstream_boolean_op, only_selected_layer) { network_interface.set_input(&InputConnector::node(upstream_boolean_op, 1), NodeInput::value(TaggedValue::BooleanOperation(operation), false), &[]); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); return only_selected_layer.to_node(); } @@ -2874,7 +2889,7 @@ impl DocumentMessageHandler { } if modified { - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SendGraph); } } diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index c7f6fc12cb..a4a1724893 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -126,7 +126,7 @@ impl MessageHandler> for } } responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } GraphOperationMessage::NewBitmapLayer { id, @@ -138,7 +138,7 @@ impl MessageHandler> for let layer = modify_inputs.create_layer(id); modify_inputs.insert_image_data(image_frame, layer); network_interface.move_layer_to_stack(layer, parent, insert_index, &[]); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } GraphOperationMessage::NewBooleanOperationLayer { id, operation, parent, insert_index } => { let mut modify_inputs = ModifyInputsContext::new(network_interface, responses); @@ -149,7 +149,7 @@ impl MessageHandler> for node_id: id, alias: "Boolean Operation".to_string(), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => { let mut modify_inputs = ModifyInputsContext::new(network_interface, responses); @@ -169,14 +169,14 @@ impl MessageHandler> for } // Move the layer and all nodes to the correct position in the network responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => { let mut modify_inputs = ModifyInputsContext::new(network_interface, responses); let layer = modify_inputs.create_layer(id); modify_inputs.insert_vector_data(subpaths, layer, true, true, true); network_interface.move_layer_to_stack(layer, parent, insert_index, &[]); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } GraphOperationMessage::NewTextLayer { id, @@ -191,7 +191,7 @@ impl MessageHandler> for modify_inputs.insert_text(text, font, typesetting, layer); network_interface.move_layer_to_stack(layer, parent, insert_index, &[]); responses.add(GraphOperationMessage::StrokeSet { layer, stroke: Stroke::default() }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } GraphOperationMessage::ResizeArtboard { layer, location, dimensions } => { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { @@ -279,7 +279,7 @@ impl MessageHandler> for }); } - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SendGraph); } diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 23a878f044..d4c201193d 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -454,7 +454,7 @@ impl<'a> ModifyInputsContext<'a> { // Refresh the render and editor UI self.responses.add(PropertiesPanelMessage::Refresh); if !skip_rerender { - self.responses.add(NodeGraphMessage::RunDocumentGraph); + self.responses.add(PortfolioMessage::CompileActiveDocument); } } @@ -462,7 +462,7 @@ impl<'a> ModifyInputsContext<'a> { let Some(path_node_id) = self.existing_node_id("Path", true) else { return }; self.network_interface.vector_modify(&path_node_id, modification_type); self.responses.add(PropertiesPanelMessage::Refresh); - self.responses.add(NodeGraphMessage::RunDocumentGraph); + self.responses.add(PortfolioMessage::CompileActiveDocument); } pub fn brush_modify(&mut self, strokes: Vec) { @@ -495,7 +495,7 @@ impl<'a> ModifyInputsContext<'a> { self.network_interface.set_input(&input_connector, input, &[]); self.responses.add(PropertiesPanelMessage::Refresh); if !skip_rerender { - self.responses.add(NodeGraphMessage::RunDocumentGraph); + self.responses.add(PortfolioMessage::CompileActiveDocument); } } } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 67f7704530..5520238a21 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -30,7 +30,6 @@ use std::collections::{HashMap, HashSet, VecDeque}; pub struct NodePropertiesContext<'a> { pub persistent_data: &'a PersistentData, pub responses: &'a mut VecDeque, - pub executor: &'a mut NodeGraphExecutor, pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], pub document_name: &'a str, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index a730f47cad..7f60d5f775 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -208,12 +208,6 @@ pub enum NodeGraphMessage { UpdateImportsExports, UpdateLayerPanel, UpdateNewNodeGraph, - UpdateTypes { - #[serde(skip)] - resolved_types: ResolvedDocumentNodeTypesDelta, - #[serde(skip)] - node_graph_errors: GraphErrors, - }, UpdateActionButtons, UpdateGraphBarRight, UpdateInSelectedNetwork, 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 d7391227c9..ae6431d9a6 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 @@ -180,7 +180,7 @@ impl<'a> MessageHandler> for NodeG responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer }); responses.add(PropertiesPanelMessage::Refresh); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } NodeGraphMessage::CreateNodeFromContextMenu { node_id, @@ -241,7 +241,7 @@ impl<'a> MessageHandler> for NodeG input_connector: InputConnector::node(node_id, input_index), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } self.wire_in_progress_from_connector = None; @@ -283,7 +283,7 @@ impl<'a> MessageHandler> for NodeG node_ids: selected_nodes.selected_nodes().cloned().collect::>(), delete_children, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SendGraph); } @@ -560,7 +560,7 @@ impl<'a> MessageHandler> for NodeG }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![encapsulating_node_id] }); responses.add(NodeGraphMessage::SendGraph); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index } => { network_interface.move_layer_to_stack(layer, parent, insert_index, selection_network_path); @@ -890,7 +890,7 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::DisconnectInput { input_connector: *disconnecting }); } // Update the frontend that the node is disconnected - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SendGraph); self.disconnecting = None; } @@ -1064,7 +1064,7 @@ impl<'a> MessageHandler> for NodeG output_connector: *output_connector, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SendGraph); } else if output_connector.is_some() && input_connector.is_none() && !self.initial_disconnecting { @@ -1222,7 +1222,7 @@ impl<'a> MessageHandler> for NodeG input_connector: *overlapping_wire, insert_node_input_index: selected_node_input_connect_index, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SendGraph); } } @@ -1273,24 +1273,24 @@ impl<'a> MessageHandler> for NodeG NodeGraphMessage::RemoveImport { import_index: usize } => { network_interface.remove_import(usize, selection_network_path); responses.add(NodeGraphMessage::SendGraph); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } NodeGraphMessage::RemoveExport { export_index: usize } => { network_interface.remove_export(usize, selection_network_path); responses.add(NodeGraphMessage::SendGraph); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } NodeGraphMessage::ReorderImport { start_index, end_index } => { network_interface.reorder_import(start_index, end_index, selection_network_path); responses.add(NodeGraphMessage::SendGraph); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } NodeGraphMessage::ReorderExport { start_index, end_index } => { network_interface.reorder_export(start_index, end_index, selection_network_path); responses.add(NodeGraphMessage::SendGraph); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } - NodeGraphMessage::RunDocumentGraph => { + PortfolioMessage::CompileActiveDocument => { responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false }); } NodeGraphMessage::ForceRunDocumentGraph => { @@ -1340,10 +1340,7 @@ impl<'a> MessageHandler> for NodeG let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else { return; }; - - let viewport_bbox = ipp.document_bounds(); - let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p)); - + let document_bbox: [DVec2; 2] = ipp.document_bounds(); let mut nodes = Vec::new(); for node_id in &self.frontend_nodes { let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else { @@ -1395,7 +1392,7 @@ impl<'a> MessageHandler> for NodeG }); responses.add(PropertiesPanelMessage::Refresh); if !(network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) { - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } } NodeGraphMessage::SetInput { input_connector, input } => { @@ -1468,7 +1465,7 @@ impl<'a> MessageHandler> for NodeG }); } if selected_nodes.selected_nodes().any(|node_id| network_interface.connected_to_output(node_id, selection_network_path)) { - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } } NodeGraphMessage::ShiftNodePosition { node_id, x, y } => { @@ -1486,7 +1483,7 @@ impl<'a> MessageHandler> for NodeG responses.add(FrontendMessage::UpdateContextMenuInformation { context_menu_information: self.context_menu.clone(), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendWires); } @@ -1521,7 +1518,7 @@ impl<'a> MessageHandler> for NodeG responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::TogglePreviewImpl { node_id }); responses.add(NodeGraphMessage::UpdateActionButtons); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } NodeGraphMessage::TogglePreviewImpl { node_id } => { network_interface.toggle_preview(node_id, selection_network_path); @@ -1606,7 +1603,7 @@ impl<'a> MessageHandler> for NodeG } NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids } => { if node_ids.iter().any(|node_id| network_interface.connected_to_output(node_id, selection_network_path)) { - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } responses.add(NodeGraphMessage::UpdateActionButtons); responses.add(NodeGraphMessage::SendGraph); @@ -1717,15 +1714,6 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SendGraph); } - NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => { - for (path, node_type) in resolved_types.add { - network_interface.resolved_types.types.insert(path.to_vec(), node_type); - } - for path in resolved_types.remove { - network_interface.resolved_types.types.remove(&path.to_vec()); - } - self.node_graph_errors = node_graph_errors; - } NodeGraphMessage::UpdateActionButtons => { if selection_network_path == breadcrumb_network_path { self.update_graph_bar_left(network_interface, breadcrumb_network_path, responses); diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index 4c659f7540..0d4b50909e 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -5,15 +5,21 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; -use crate::node_graph_executor::NodeGraphExecutor; -#[derive(ExtractField)] -pub struct PropertiesPanelMessageContext<'a> { +use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; +use graph_craft::document::NodeId; +pub struct PropertiesPanelMessageHandlerData<'a> { + pub network_interface: &'a mut NodeNetworkInterface, + pub selection_network_path: &'a [NodeId], + pub document_name: &'a str, +} + +use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; +use graph_craft::document::NodeId; +pub struct PropertiesPanelMessageHandlerData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], pub document_name: &'a str, - pub executor: &'a mut NodeGraphExecutor, - pub persistent_data: &'a PersistentData, } #[derive(Debug, Clone, Default, ExtractField)] @@ -26,9 +32,7 @@ impl MessageHandler> f network_interface, selection_network_path, document_name, - executor, - persistent_data, - } = context; + } = data; match message { PropertiesPanelMessage::Clear => { @@ -44,7 +48,6 @@ impl MessageHandler> f network_interface, selection_network_path, document_name, - executor, }; let properties_sections = NodeGraphMessageHandler::collate_properties(&mut node_properties_context); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 404d3486a0..acaa7364fb 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -11,12 +11,14 @@ use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode; use bezier_rs::Subpath; use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; +use graph_craft::document::{DocumentNode, DocumentNodeImplementation, InputConnector, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork, OutputConnector}; use graph_craft::{Type, concrete}; use graphene_std::math::quad::Quad; use graphene_std::transform::Footprint; +use graphene_std::uuid::{CompiledProtonodeInput, NodeId}; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::{PointId, VectorData, VectorModificationType}; +use graphene_std::{CompiledProtonodeInput, SNI}; use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes; use interpreted_executor::node_registry::NODE_REGISTRY; use serde_json::{Value, json}; @@ -40,6 +42,8 @@ pub struct NodeNetworkInterface { pub resolved_types: ResolvedDocumentNodeTypes, #[serde(skip)] transaction_status: TransactionStatus, + #[serde(skip)] + current_hash: u64, } impl Clone for NodeNetworkInterface { @@ -478,6 +482,13 @@ impl NodeNetworkInterface { node_template } + pub fn hash_changed(&mut self) -> bool { + let old_hash = self.current_hash; + let new_hash = self.network.current_hash(); + self.current_hash = new_hash; + old_hash != new_hash + } + /// Try and get the [`DocumentNodeDefinition`] for a node pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> { let metadata = self.node_metadata(&node_id, network_path)?; @@ -501,66 +512,86 @@ impl NodeNetworkInterface { } } - /// Try and get the [`Type`] for any [`InputConnector`] based on the `self.resolved_types`. - fn node_type_from_compiled(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<(Type, TypeSource)> { - let (node_id, input_index) = match *input_connector { - InputConnector::Node { node_id, input_index } => (node_id, input_index), - InputConnector::Export(export_index) => { - let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else { - // The outermost network export defaults to an ArtboardGroupTable. - return Some((concrete!(graphene_std::ArtboardGroupTable), TypeSource::OuterMostExportDefault)); - }; - - let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path); - return Some(output_type); + pub fn downstream_caller_from_output(&self, output_connector: OutputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> { + match output_connector { + OutputConnector::Node { node_id, output_index } => match self.implementation(&node_id, network_path)? { + DocumentNodeImplementation::Network(node_network) => { + let mut nested_path = network_path.to_vec(); + nested_path.push(node_id); + self.downstream_caller_from_input(InputConnector::Export(output_index), &nested_path) + } + DocumentNodeImplementation::ProtoNode(_) => self.node_metadata(&node_id, network_path)?.transient_metadata.caller.as_ref(), + DocumentNodeImplementation::Extract => todo!(), + }, + OutputConnector::Import(import_index) => { + let mut encapsulating_path = network_path.to_vec(); + let node_id = encapsulating_path.pop().expect("No imports in document network"); + self.downstream_caller_from_input(InputConnector::node(node_id, import_index), &encapsulating_path) } + } + } + // Returns the path and input index to the protonode which called the input, which has to be the same every time is is called for a given input. + // This has to be done by iterating upstream, since a downstream traversal may lead to an uncompiled branch. + // This requires that value inputs store their caller. Caller input metadata from compilation has to be stored for + pub fn downstream_caller_from_input(&self, &input_connector: InputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> { + // Cases: Node/Value input to protonode, Node/Value input to network node + let input = self.input_from_connector(input_connector, network_path)?; + let caller_input = match input { + NodeInput::Node { node_id, output_index, lambda } => { + match self.implementation(node_id, network_path)? { + DocumentNodeImplementation::Network(node_network) => { + // Continue traversal within network + let mut nested_path = network_path.to_vec(); + nested_path.push(*node_id); + self.downstream_caller_from_input(InputConnector::Export(*output_index), &nested_path) + } + DocumentNodeImplementation::ProtoNode(proto_node_identifier) => self.node_metadata(node_id, network_path)?.transient_metadata.caller.as_ref(), + // If connected to a protonode, use the data in the node metadata + DocumentNodeImplementation::Extract => todo!(), + } + } + // Can either be an input to a protonode, network node, or export + NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Reflection(_) => match input_connector { + InputConnector::Node { node_id, input_index } => self.input_metadata(node_id, *index, network_path)?.transient_metadata.caller.as_ref(), + InputConnector::Export(export_index) => self.network_metadata(network_path)?.transient_metadata.callers.get(export_index)?.as_ref(), + }, + NodeInput::Network { import_index } => { + let mut encapsulating_path = network_path.to_vec(); + let node_id = encapsulating_path.pop().expect("No imports in document network"); + self.downstream_caller_from_input(InputConnector::node(node_id, *import_index), &encapsulating_path) + } + NodeInput::Inline(inline_rust) => None, }; - let Some(node) = self.document_node(&node_id, network_path) else { - log::error!("Could not get node {node_id} in input_type"); + let Some(caller_input) = caller_input else { + log::error!("Could not get compiled caller input for input: {:?}", input_connector); return None; }; - // If the input_connector is a NodeInput::Value, return the type of the tagged value. - if let Some(value) = node.inputs.get(input_index).and_then(|input| input.as_value()) { - return Some((value.ty(), TypeSource::TaggedValue)); - } - let node_id_path = [network_path, &[node_id]].concat(); - match &node.implementation { - DocumentNodeImplementation::Network(_nested_network) => { - // Attempt to resolve where this import is within the nested network (it may be connected to the node or directly to an export) - let outwards_wires = self.outward_wires(&node_id_path); - let inputs_using_import = outwards_wires.and_then(|outwards_wires| outwards_wires.get(&OutputConnector::Import(input_index))); - let first_input = inputs_using_import.and_then(|input| input.first()).copied(); + Some(caller_input) + } - if inputs_using_import.is_some_and(|inputs| inputs.len() > 1) { - warn!("Found multiple inputs using an import. Using the type of the first one."); - } - - if let Some(input_connector) = first_input { - self.node_type_from_compiled(&input_connector, &node_id_path) - } - // Nothing is connected to the import - else { - None - } + pub fn take_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { + let Some(network) = self.network_mut(network_path) else { + log::error!("Could not get network in input_from_connector"); + return None; + }; + let input = match input_connector { + InputConnector::Node { node_id, input_index } => { + let Some(node) = network.nodes.get_mut(node_id) else { + log::error!("Could not get node {node_id} in input_from_connector"); + return None; + }; + node.inputs.get_mut(*input_index) } - DocumentNodeImplementation::ProtoNode(_) => { - // If a node has manual composition, then offset the input index by 1 since the proto node also includes the type of the input passed through manual composition. - let manual_composition_offset = if node.manual_composition.is_some() { 1 } else { 0 }; - self.resolved_types - .types - .get(node_id_path.as_slice()) - .and_then(|node_types| node_types.inputs.get(input_index + manual_composition_offset).cloned()) - .map(|node_types| (node_types, TypeSource::Compiled)) - } - DocumentNodeImplementation::Extract => None, - } + InputConnector::Export(export_index) => network.exports.get_mut(*export_index), + }; + input.map(|input| std::mem::replace(input, NodeInput::value(TaggedValue::None, true))) } /// Guess the type from the node based on a document node default or a random protonode definition. - fn guess_type_from_node(&mut self, network_path: &mut Vec, node_id: NodeId, input_index: usize) -> (Type, TypeSource) { + fn guess_type_from_node(&mut self, node_id: NodeId, input_index: usize, network_path: &[NodeId]) -> (Type, TypeSource) { // Try and get the default value from the document node definition if let Some(value) = self - .get_node_definition(network_path, node_id) + .node_definition(node_id, network_path) .and_then(|definition| definition.node_template.document_node.inputs.get(input_index)) .and_then(|input| input.as_value()) { @@ -571,21 +602,21 @@ impl NodeNetworkInterface { return (concrete!(()), TypeSource::Error("node id {node_id:?} not in network {network_path:?}")); }; - let node_id_path = [network_path.as_slice(), &[node_id]].concat(); + let mut node_id_path = network_path.to_vec(); + node_id_path.push(node_id); + match &node.implementation { DocumentNodeImplementation::ProtoNode(protonode) => { let Some(node_types) = random_protonode_implementation(protonode) else { return (concrete!(()), TypeSource::Error("could not resolve protonode")); }; - let skip_footprint = if node.manual_composition.is_some() { 1 } else { 0 }; - - let Some(input_type) = std::iter::once(node_types.call_argument.clone()).chain(node_types.inputs.clone()).nth(input_index + skip_footprint) else { + let Some(input_type) = node_types.inputs.get(input_index) else { log::error!("Could not get type"); return (concrete!(()), TypeSource::Error("could not get the protonode's input")); }; - (input_type, TypeSource::RandomProtonodeImplementation) + (input_type.clone(), TypeSource::RandomProtonodeImplementation) } DocumentNodeImplementation::Network(_network) => { // Attempt to resolve where this import is within the nested network @@ -598,9 +629,10 @@ impl NodeNetworkInterface { input_index: child_input_index, }) = first_input { - network_path.push(node_id); - let result = self.guess_type_from_node(network_path, child_id, child_input_index); - network_path.pop(); + let mut inner_path = network_path.to_vec(); + inner_path.push(node_id); + let result = self.guess_type_from_node(child_id, child_input_index, inner_path); + inner_path.pop(); return result; } @@ -613,8 +645,11 @@ impl NodeNetworkInterface { /// Get the [`Type`] for any InputConnector pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { - if let Some(result) = self.node_type_from_compiled(input_connector, network_path) { - return result; + if let Some(compiled_type) = self + .downstream_caller_from_input(input_connector, network_path) + .and_then(|(sni, input_index)| self.resolved_types.get(sni).and_then(|protonode_input_types| protonode_input_types.get(*input_index))) + { + return (compiled_type.clone(), TypeSource::Compiled); } // Resolve types from proto nodes in node_registry @@ -622,9 +657,57 @@ impl NodeNetworkInterface { return (concrete!(()), TypeSource::Error("input connector is not a node")); }; - // TODO: Once there is type inference (#1621), replace this workaround approach when disconnecting node inputs with NodeInput::Node(ToDefaultNode), - // TODO: which would be a new node that implements the Default trait (i.e. `Default::default()`) - self.guess_type_from_node(&mut network_path.to_vec(), node_id, input_connector.input_index()) + self.guess_type_from_node(node_id, input_connector.input_index(), network_path); + } + + pub fn compiled_output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<&Type> { + let (sni, input_index) = self.downstream_caller_from_output(output_connector, network_path)?; + let protonode_input_types = self.resolved_types.get(sni)?; + protonode_input_types.get(*input_index) + } + + pub fn output_type(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { + if let Some(output_type) = self.compiled_output_type(output_connector, network_path) { + return (output_type.clone(), TypeSource::Compiled); + } + (concrete!(()), TypeSource::Error("Not compiled")) + } + + pub fn add_type(&mut self, sni: SNI, input_types: Vec) { + self.resolved_types.insert(sni, input_types); + } + + pub fn remove_type(&mut self, sni: SNI) { + self.resolved_types.remove(sni); + } + + pub fn set_node_caller(&mut self, node: &NodeId, caller: CompiledProtonodeInput, network_path: &[NodeId]) { + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + return; + }; + metadata.transient_metadata.caller = Some(caller); + } + + pub fn set_input_caller(&mut self, input_connector: &InputConnector, caller: CompiledProtonodeInput, network_path: &[NodeId]) { + match input_connector { + InputConnector::Node { node_id, input_index } => { + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + return; + }; + let Some(input_metadata) = metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { + log::error!("input metadata must exist when setting input caller for node {}, input index {}", node_id, input_index); + return; + }; + input_metadata.transient_metadata.caller = Some(caller); + } + InputConnector::Export(export_index) => { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { + return; + }; + network_metadata.transient_metadata.callers.resize(*export_index + 1, None); + network_metadata.transient_metadata.callers[*export_index] = Some(caller); + } + } } pub fn valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec { @@ -1496,10 +1579,7 @@ impl NodeNetworkInterface { let mut node_metadata = DocumentNodeMetadata::default(); node.inputs = old_node.inputs; - node.manual_composition = old_node.manual_composition; node.visible = old_node.visible; - node.skip_deduplication = old_node.skip_deduplication; - node.original_location = old_node.original_location; node_metadata.persistent_metadata.display_name = old_node.alias; node_metadata.persistent_metadata.reference = if old_node.name.is_empty() { None } else { Some(old_node.name) }; node_metadata.persistent_metadata.has_primary_output = old_node.has_primary_output; @@ -1539,7 +1619,7 @@ impl NodeNetworkInterface { network: node_network, network_metadata, document_metadata: DocumentMetadata::default(), - resolved_types: ResolvedDocumentNodeTypes::default(), + resolved_types: HashMap::new(), transaction_status: TransactionStatus::Finished, } } @@ -6060,94 +6140,6 @@ pub enum ImportOrExport { Export(usize), } -/// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum InputConnector { - #[serde(rename = "node")] - Node { - #[serde(rename = "nodeId")] - node_id: NodeId, - #[serde(rename = "inputIndex")] - input_index: usize, - }, - #[serde(rename = "export")] - Export(usize), -} - -impl Default for InputConnector { - fn default() -> Self { - InputConnector::Export(0) - } -} - -impl InputConnector { - pub fn node(node_id: NodeId, input_index: usize) -> Self { - InputConnector::Node { node_id, input_index } - } - - pub fn input_index(&self) -> usize { - match self { - InputConnector::Node { input_index, .. } => *input_index, - InputConnector::Export(input_index) => *input_index, - } - } - - pub fn node_id(&self) -> Option { - match self { - InputConnector::Node { node_id, .. } => Some(*node_id), - _ => None, - } - } -} - -/// Represents an output connector -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum OutputConnector { - #[serde(rename = "node")] - Node { - #[serde(rename = "nodeId")] - node_id: NodeId, - #[serde(rename = "outputIndex")] - output_index: usize, - }, - #[serde(rename = "import")] - Import(usize), -} - -impl Default for OutputConnector { - fn default() -> Self { - OutputConnector::Import(0) - } -} - -impl OutputConnector { - pub fn node(node_id: NodeId, output_index: usize) -> Self { - OutputConnector::Node { node_id, output_index } - } - - pub fn index(&self) -> usize { - match self { - OutputConnector::Node { output_index, .. } => *output_index, - OutputConnector::Import(output_index) => *output_index, - } - } - - pub fn node_id(&self) -> Option { - match self { - OutputConnector::Node { node_id, .. } => Some(*node_id), - _ => None, - } - } - - pub fn from_input(input: &NodeInput) -> Option { - match input { - NodeInput::Network { import_index, .. } => Some(Self::Import(*import_index)), - NodeInput::Node { node_id, output_index, .. } => Some(Self::node(*node_id, *output_index)), - _ => None, - } - } -} - #[derive(Debug, Clone)] pub struct Ports { input_ports: Vec<(usize, ClickTarget)>, @@ -6381,6 +6373,7 @@ pub struct NodeNetworkTransientMetadata { pub rounded_network_edge_distance: TransientMetadata, // Wires from the exports pub wires: Vec>, + pub callers: Vec>, } #[derive(Debug, Clone)] @@ -6568,8 +6561,8 @@ impl InputPersistentMetadata { #[derive(Debug, Clone, Default)] struct InputTransientMetadata { wire: TransientMetadata, - // downstream_protonode: populated for all inputs after each compile - // types: populated for each protonode after each + caller: Option, + input_type: Option, } // TODO: Eventually remove this migration document upgrade code @@ -6883,6 +6876,8 @@ pub struct DocumentNodeTransientMetadata { pub click_targets: TransientMetadata, // Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer. pub node_type_metadata: NodeTypeTransientMetadata, + // Stores the caller input since it will be reached through an upstream traversal, but all data is stored per input. + pub caller: Option, } #[derive(Debug, Clone)] diff --git a/editor/src/messages/portfolio/document/utility_types/nodes.rs b/editor/src/messages/portfolio/document/utility_types/nodes.rs index 66369026b3..6f88a9efae 100644 --- a/editor/src/messages/portfolio/document/utility_types/nodes.rs +++ b/editor/src/messages/portfolio/document/utility_types/nodes.rs @@ -2,7 +2,8 @@ use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use super::network_interface::NodeNetworkInterface; use crate::messages::tool::common_functionality::graph_modification_utils; use glam::DVec2; -use graph_craft::document::{NodeId, NodeNetwork}; +use graph_craft::document::NodeNetwork; +use graphene_std::uuid::NodeId; use serde::ser::SerializeStruct; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)] diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 4fb756fc4a..9eaccf5429 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -1,11 +1,17 @@ +use std::sync::Arc; + use super::document::utility_types::document_metadata::LayerNodeIdentifier; use super::utility_types::PanelType; use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::prelude::*; -use graphene_std::Color; +use crate::node_graph_executor::CompilationResponse; +use graph_craft::document::CompilationMetadata; use graphene_std::raster::Image; +use graphene_std::renderer::RenderMetadata; use graphene_std::text::Font; +use graphene_std::uuid::CompiledProtonodeInput; +use graphene_std::{Color, IntrospectMode}; #[impl_message(Message, Portfolio)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -18,8 +24,24 @@ pub enum PortfolioMessage { #[child] Spreadsheet(SpreadsheetMessage), - // Messages - Init, + // Sends a request to compile the network. Should occur when any value, preference, or font changes + CompileActiveDocument, + // Sends a request to evaluate the network. Should occur when any context value changes.2 + EvaluateActiveDocument, + + // Processes the compilation response and updates the data stored in the network interface for the active document + // TODO: Add document ID in response for stability + ProcessCompilationResponse { + compilation_metadata: CompilationMetadata, + }, + ProcessEvaluationResponse { + evaluation_metadata: RenderMetadata, + #[serde(skip)] + introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box)>, + }, + ProcessThumbnails { + inputs_to_render: HashSet, + }, DocumentPassMessage { document_id: DocumentId, message: DocumentMessage, @@ -48,7 +70,6 @@ pub enum PortfolioMessage { document_id: DocumentId, }, DestroyAllDocuments, - EditorPreferences, FontLoaded { font_family: String, font_style: String, @@ -120,13 +141,7 @@ pub enum PortfolioMessage { bounds: ExportBounds, transparent_background: bool, }, - SubmitActiveGraphRender, - SubmitGraphRender { - document_id: DocumentId, - ignore_hash: bool, - }, ToggleRulers, UpdateDocumentWidgets, UpdateOpenDocumentsList, - UpdateVelloPreference, } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index daa81274a3..a0b3d6a5f4 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -3,19 +3,18 @@ use super::document::utility_types::network_interface; use super::spreadsheet::SpreadsheetMessageHandler; use super::utility_types::{PanelType, PersistentData}; use crate::application::generate_uuid; -use crate::consts::DEFAULT_DOCUMENT_NAME; -use crate::messages::animation::TimingInformation; +use crate::consts::{DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX}; use crate::messages::debug::utility_types::MessageLoggingVerbosity; use crate::messages::dialog::simple_dialogs; use crate::messages::frontend::utility_types::FrontendDocumentDetails; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::DocumentMessageContext; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; -use crate::messages::portfolio::document::node_graph::document_node_definitions; +use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; -use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector; use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document_migration::*; +use crate::messages::portfolio::spreadsheet::{InspectInputConnector, SpreadsheetMessageHandlerData}; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; @@ -45,6 +44,7 @@ pub struct PortfolioMessageHandler { active_panel: PanelType, pub(crate) active_document_id: Option, copy_buffer: [Vec; INTERNAL_CLIPBOARD_COUNT as usize], + // Data that persists between documents pub persistent_data: PersistentData, pub executor: NodeGraphExecutor, pub selection_mode: SelectionMode, @@ -52,6 +52,11 @@ pub struct PortfolioMessageHandler { pub spreadsheet: SpreadsheetMessageHandler, device_pixel_ratio: Option, pub reset_node_definitions_on_open: bool, + // Data from the node graph. Data for inputs are set to be collected on each evaluation, and added on the evaluation response + // Data from old nodes get deleted after a compilation + pub introspected_input_data: HashMap>, + pub downcasted_input_data: HashMap, + pub context_data: HashMap, } #[message_handler_data] @@ -100,7 +105,7 @@ impl MessageHandler> for Portfolio self.menu_bar_message_handler.process_message(message, responses, ()); } PortfolioMessage::Spreadsheet(message) => { - self.spreadsheet.process_message(message, responses, ()); + self.spreadsheet.process_message(message, responses, SpreadsheetMessageHandlerData {introspected_data}); } PortfolioMessage::Document(message) => { if let Some(document_id) = self.active_document_id { @@ -109,7 +114,6 @@ impl MessageHandler> for Portfolio document_id, ipp, persistent_data: &self.persistent_data, - executor: &mut self.executor, current_tool, preferences, device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.), @@ -143,7 +147,6 @@ impl MessageHandler> for Portfolio document_id, ipp, persistent_data: &self.persistent_data, - executor: &mut self.executor, current_tool, preferences, device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.), @@ -331,25 +334,11 @@ impl MessageHandler> for Portfolio data, } => { let font = Font::new(font_family, font_style); - - self.persistent_data.font_cache.insert(font, preview_url, data); - self.executor.update_font_cache(self.persistent_data.font_cache.clone()); - for document_id in self.document_ids.iter() { - let inspect_node = self.inspect_node_id(); - let _ = self.executor.submit_node_graph_evaluation( - self.documents.get_mut(document_id).expect("Tried to render non-existent document"), - ipp.viewport_bounds.size().as_uvec2(), - timing_information, - inspect_node, - true, - ); - } - - if self.active_document_mut().is_some() { - responses.add(NodeGraphMessage::RunDocumentGraph); - } + let mut font_cache = self.persistent_data.font_cache.as_ref().clone(); + font_cache.insert(font, preview_url, data); + self.persistent_data.font_cache = Arc::new(font_cache); + responses.add(PortfolioMessage::CompileActiveDocument); } - PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()), PortfolioMessage::Import => { // This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages responses.add(FrontendMessage::TriggerImport); @@ -448,7 +437,8 @@ impl MessageHandler> for Portfolio document_migration_upgrades(&mut document, reset_node_definitions_on_open); // Ensure each node has the metadata for its inputs - for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() { + for (mut path, node) in document.network_interface.document_network().clone().recursive_nodes() { + let node_id = path.pop().unwrap(); document.network_interface.validate_input_metadata(node_id, node, &path); document.network_interface.validate_display_name_metadata(node_id, &path); document.network_interface.validate_output_names(node_id, node, &path); @@ -510,7 +500,7 @@ impl MessageHandler> for Portfolio for entry in self.copy_buffer[clipboard as usize].iter().rev() { paste(entry, responses, &mut all_new_ids) } - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids }); } PortfolioMessage::PasteSerializedData { data } => { @@ -539,9 +529,9 @@ impl MessageHandler> for Portfolio layers.push(layer); } - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids }); - responses.add(Message::StartBuffer); + // responses.add(Message::StartBuffer); responses.add(PortfolioMessage::CenterPastedLayers { layers }); } } @@ -648,7 +638,7 @@ impl MessageHandler> for Portfolio } } - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } } PortfolioMessage::PasteImage { @@ -674,12 +664,12 @@ impl MessageHandler> for Portfolio if create_document { // Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }); // TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead // Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); } } @@ -706,12 +696,12 @@ impl MessageHandler> for Portfolio if create_document { // Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }); // TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead // Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); } } @@ -758,7 +748,7 @@ impl MessageHandler> for Portfolio responses.add(BroadcastEvent::ToolAbort); responses.add(BroadcastEvent::SelectionChanged); responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open }); if node_graph_open { responses.add(NodeGraphMessage::UpdateGraphBarRight); @@ -777,14 +767,332 @@ impl MessageHandler> for Portfolio responses.add(PropertiesPanelMessage::Clear); } } - PortfolioMessage::SubmitDocumentExport { + PortfolioMessage::CompileActiveDocument => { + let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { + log::error!("Tried to render non-existent document: {:?}", document_id); + return; + }; + if document.network_interface.hash_changed() { + self.executor.submit_node_graph_compilation(CompilationRequest { + network: document.network_interface.document_network().clone(), + font_cache: self.persistent_data.font_cache.clone(), + editor_metadata: EditorMetadata { + #[cfg(any(feature = "resvg", feature = "vello"))] + use_vello: preferences.use_vello(), + #[cfg(not(any(feature = "resvg", feature = "vello")))] + use_vello: false, + hide_artboards: false, + for_export: false, + view_mode: document.view_mode, + transform_to_viewport: true, + }, + }); + } + // Always evaluate after a recompile + responses.add(PortfolioMessage::EvaluateActiveDocument); + } + PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => { + let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { + log::error!("Tried to render non-existent document: {:?}", self.active_document_id); + return; + }; + for (AbsoluteInputConnector { network_path, connector }, caller) in compilation_metadata.protonode_callers_for_value { + document.network_interface.set_input_caller(connector, caller, &network_path) + } + for (protonode_path, caller) in compilation_metadata.protonode_callers_for_node { + let (node_id, network_path) = protonode_path.to_vec().split_last().expect("Protonode path cannot be empty"); + document.network_interface.set_node_caller(node_id, caller, &network_path) + } + for (sni, input_types) in compilation_metadata.types_to_add { + document.network_interface.add_type(sni, input_types); + } + for ((sni, number_of_inputs)) in compilation_metadata.types_to_remove { + // Removed saves type of the document node + document.network_interface.remove_type(sni); + // Remove introspection data for all monitor nodes and the thumbnails + let mut cleared_thumbnails = Vec::new(); + for monitor_index in 0..number_of_inputs { + self.introspected_input_data.remove((sni, monitor_index)); + self.downcasted_input_data.remove((sni, monitor_index)); + self.context_data.remove((sni, monitor_index)); + cleared_thumbnails.push(NodeId(sni.0+monitor_index as u64 +1)); + } + responses.add(FrontendMessage::UpdateThumbnails { add: Vec::new(), clear: cleared_thumbnails }) + } + } + PortfolioMessage::EvaluateActiveDocument => { + let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { + log::error!("Tried to render non-existent document: {:?}", self.active_document_id); + return; + }; + + // Get all the inputs to save data for. This includes vector modify, thumbnails, and spreadsheet data + let inputs_to_monitor = HashSet::new(); + let inputs_to_render = HashSet::new(); + let inspect_input = None; + + // Get the protonode input for all side layer inputs connected to the export in the document network for thumbnails in the layer panel + for caller in document + .network_interface + .document_metadata() + .all_layers() + .filter_map(|layer| { + let input = InputConnector::Node { + node_id: layer.to_node(), + input_index: 1, + }; + document + .network_interface + .downstream_caller_from_input(&input, &[]) + }) { + inputs_to_monitor.insert((*caller, IntrospectMode::Data)); + inputs_to_render.insert(*caller); + } + + // Save data for all inputs in the viewed node graph + if document.graph_view_overlay_open { + let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else { + return; + }; + for (export_index, export) in viewed_network.exports.iter().enumerate() { + if let Some(caller) = document + .network_interface + .downstream_caller_from_input(InputConnector::Export(export_index), &document.breadcrumb_network_path) + { + inputs_to_monitor.push((*caller, IntrospectMode::Data)) + }; + if let Some(NodeInput::Node { node_id, .. }) = export { + for upstream_node in document + .network_interface + .upstream_flow_back_from_nodes(vec![*node_id], &document.breadcrumb_network_path, network_interface::FlowType::UpstreamFlow) + { + let node = viewed_network.nodes[&upstream_node]; + for (index, _) in node.inputs.iter().enumerate().filter(|(_, node_input)| node_input.is_exposed()) { + if let Some(caller) = document + .network_interface + .downstream_caller_from_input(InputConnector::Node(node_id, index), &document.breadcrumb_network_path) + { + inputs_to_monitor.insert((*caller, IntrospectMode::Data)); + inputs_to_render.insert(*caller); + }; + } + } + } + } + } + + // Save vector data for all path/transform nodes in the document network + match document.network_interface.input_from_connector(&InputConnector::Export(0), &[]) { + Some(NodeInput::Node { node_id, .. }) => { + for upstream_node in document.network_interface.upstream_flow_back_from_nodes(vec![*node_id], &[], network_interface::FlowType::UpstreamFlow) { + let reference = document.network_interface.reference(node_id, &[]).unwrap_or_default().as_deref().unwrap_or_default(); + if reference == "Path" || reference == "Transform" { + let input_connector = InputConnector::Node { node_id, input_index: 0 }; + let Some(downstream_caller) = document.network_interface.downstream_caller_from_input(&input_connector, &[]) else{ + log::error!("could not get downstream caller for node : {:?}", node_id); + continue; + }; + inputs_to_monitor.push(*downstream_caller) + } + } + }, + _ => {}, + } + + // Introspect data for the currently selected node (eventually thumbnail) if the spreadsheet view is open + if self.spreadsheet.spreadsheet_view_open { + let selected_network_path = &document.selection_network_path; + // TODO: Replace with selected thumbnail + if let Some(selected_node) = document.network_interface.selected_nodes_in_nested_network(selected_network_path).and_then(|selected_nodes| { + if selected_nodes.0.len() == 1 { + selected_nodes.0.first().copied() + } else { + None + } + }) { + // TODO: Introspect any input rather than just the first input of the selected node + let selected_connector = InputConnector::Node { node_id: selected_node, input_index: 0 }; + let Some(caller) = document + .network_interface + .downstream_caller_from_input(&selected_connector, selected_network_path) else { + log::error!("Could not get downstream caller for {:?}", selected_node); + }; + inputs_to_monitor.push((*caller, IntrospectMode::Data)); + inspect_input = Some(InspectInputConnector { input_connector: AbsoluteInputConnector { network_path: selected_network_path.clone(), connector: selected_connector }, protonode_input: *caller }); + } + } + + // let animation_time = match animation.timing_information().animation_time { + // AnimationState::Stopped => 0., + // AnimationState::Playing { start } => ipp.time - start, + // AnimationState::Paused { start, pause_time } => pause_time - start, + // }; + + let mut context = EditorContext::default(); + // context.footprint = Some(Footprint { + // transform: document.metadata().document_to_viewport, + // resolution: ipp.viewport_bounds.size().as_uvec2(), + // quality: RenderQuality::Full, + // }); + // context.animation_time = Some(animation_time); + // context.real_time = Some(ipp.time); + // context.downstream_transform = Some(DAffine2::IDENTITY); + let render_config = RenderConfig { + viewport: Footprint { + transform: document.metadata().document_to_viewport, + resolution: ipp.viewport_bounds.size().as_uvec2(), + ..Default::default() + }, + time: animation.timing_information(), + #[cfg(any(feature = "resvg", feature = "vello"))] + export_format: graphene_std::application_io::ExportFormat::Canvas, + #[cfg(not(any(feature = "resvg", feature = "vello")))] + export_format: graphene_std::application_io::ExportFormat::Svg, + view_mode: document.view_mode, + hide_artboards: false, + for_export: false, + }; + + context.render_config = render_config; + + self.executor.submit_node_graph_evaluation( + context, + inputs_to_monitor, + None, + None, + ); + + // Queue messages to be run after the evaluation returns data for the inputs to monitor + responses.add(Message::StartQueue); + if let Some(inspect_input) = inspect_input { + responses.add(SpreadsheetMessage::UpdateLayout { inpect_input }); + } + responses.add(PortfolioMessage::ProcessThumbnails {inputs_to_render}); + responses.add(Message::EndQueue); + } + PortfolioMessage::ProcessEvaluationResponse { evaluation_metadata, introspected_inputs } => { + let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { + log::error!("Tried to render non-existent document: {:?}", self.active_document_id); + return; + }; + + for (input, mode, data) in introspected_inputs { + + match mode { + IntrospectMode::Input => { + let Some(context) = data.downcast_ref() + self.introspected_input_data.extend(introspected_inputs); + + }, + IntrospectMode::Data => { + self.introspected_input_data.extend(introspected_inputs); + }, + } + } + + let RenderMetadata { + upstream_footprints: footprints, + local_transforms, + click_targets, + clip_targets, + } = evaluation_metadata; + responses.add(DocumentMessage::UpdateUpstreamTransforms { + upstream_footprints: footprints, + local_transforms, + }); + responses.add(DocumentMessage::UpdateClickTargets { click_targets }); + responses.add(DocumentMessage::UpdateClipTargets { clip_targets }); + responses.add(DocumentMessage::RenderScrollbars); + responses.add(DocumentMessage::RenderRulers); + responses.add(OverlaysMessage::Draw); + // match document.animation_state { + // AnimationState::Playing { .. } => responses.add(PortfolioMessage::EvaluateActiveDocument), + // _ => {} + // }; + }, + PortfolioMessage::ProcessThumbnails { inputs_to_render } => { + let mut thumbnail_response = ThumbnailRenderResponse::default(); + for thumbnail_input in inputs_to_render { + let monitor_node_id = thumbnail_input.0.0 + thumbnail_input.1 as u64 + 1; + match self.try_render_thumbnail(&thumbnail_input) { + ThumbnailRenderResult::NoChange => {} + ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(NodeId(monitor_node_id)), + ThumbnailRenderResult::UpdateThumbnail(thumbnail) => { + thumbnail_response.add.push((NodeId(monitor_node_id), thumbnail)); + }, + } + } + responses.add(FrontendMessage::UpdateThumbnails { add: thumbnail_response.add, clear: thumbnail_response.clear }) + }, + PortfolioMessage::ActiveDocumentExport { file_name, file_type, + animation_export_data, scale_factor, bounds, transparent_background, } => { let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document"); + + // Update the scope inputs with the render settings + // self.executor.submit_node_graph_compilation(CompilationRequest { + // network: document.network_interface.document_network().clone(), + // font_cache: self.persistent_data.font_cache.clone(), + // editor_metadata: EditorMetadata { + // #[cfg(any(feature = "resvg", feature = "vello"))] + // use_vello: preferences.use_vello(), + // #[cfg(not(any(feature = "resvg", feature = "vello")))] + // use_vello: false, + // hide_artboards: transparent_background, + // for_export: true, + // view_mode: document.view_mode, + // transform_to_viewport: true, + // }, + // }); + + let document_to_viewport = document.metadata().document_to_viewport; + // Calculate the bounding box of the region to be exported + let document_bounds = match bounds { + ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!transparent_background), + ExportBounds::Selection => document.network_interface.selected_bounds_document_space(!transparent_background, &[]), + ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id), + // ExportBounds::Viewport => ipp.document_bounds(document_to_viewport), + } + .ok_or_else(|| "No bounding box".to_string())?; + + let size = document_bounds[1] - document_bounds[0]; + let scaled_size = size * scale_factor; + let transform = DAffine2::from_translation(document_bounds[0]).inverse(); + let mut context = EditorContext::default(); + // context.footprint = Footprint { + // document_to_viewport: DAffine2::from_scale(DVec2::splat(scale_factor)) * transform, + // resolution: scaled_size.as_uvec2(), + // ..Default::default() + // }; + // context.real_time = Some(ipp.time); + // context.downstream_transform = Some(DAffine2::IDENTITY); + + let render_config = RenderConfig { + viewport: Footprint { + transform: DAffine2::from_scale(DVec2::splat(scale_factor)) * transform, + resolution: (size * scale_factor).as_uvec2(), + ..Default::default() + }, + time: Default::default(), + export_format: graphene_std::application_io::ExportFormat::Svg, + view_mode: document.view_mode, + hide_artboards: transparent_background, + for_export: true, + }; + + context.render_config = render_config; + // Special handling for exporting the artwork + let file_suffix = &format!(".{file_type:?}").to_lowercase(); + let file_name = match file_name.ends_with(FILE_SAVE_SUFFIX) { + true => file_name.replace(FILE_SAVE_SUFFIX, file_suffix), + false => file_name + file_suffix, + }; + let export_config = ExportConfig { file_name, file_type, @@ -793,37 +1101,76 @@ impl MessageHandler> for Portfolio transparent_background, ..Default::default() }; - let result = self.executor.submit_document_export(document, export_config); - if let Err(description) = result { - responses.add(DialogMessage::DisplayDialogError { - title: "Unable to export document".to_string(), - description, - }); - } - } - PortfolioMessage::SubmitActiveGraphRender => { - if let Some(document_id) = self.active_document_id { - responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false }); - } - } - PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => { - let inspect_node = self.inspect_node_id(); - let result = self.executor.submit_node_graph_evaluation( - self.documents.get_mut(&document_id).expect("Tried to render non-existent document"), - ipp.viewport_bounds.size().as_uvec2(), - timing_information, - inspect_node, - ignore_hash, - ); + self.executor.submit_node_graph_evaluation( + context, + Vec::new(), + None, + Some(ExportConfig { + file_name, + file_type, + scale_factor, + bounds, + transparent_background, + size: scaled_size, + }), + ); - if let Err(description) = result { - responses.add(DialogMessage::DisplayDialogError { - title: "Unable to update node graph".to_string(), - description, - }); - } + // if let Some((start, end, fps)) = animation_export_data { + // let total_frames = ((start - end) * fps) as u32; + // for frame_index in 0..total_frames { + // context.animation_time = Some(start + (frame_index as f64) / fps); + // self.executor.submit_node_graph_evaluation( + // context.clone(), + // Vec::new(), + // None, + // Some(ExportConfig { + // file_name, + // save_render: frame_index == (total_frames - 1), + // file_type, + // size: scaled_size, + // fps: Some(fps), + // }), + // ); + // } + // } else { + // let animation_time = match document.animation_state { + // AnimationState::Stopped => 0., + // AnimationState::Playing { start } => start, + // AnimationState::Paused { start, pause_time } => pause_time, + // }; + // context.animation_time = Some(animation_time); + // self.executor.submit_node_graph_evaluation( + // EditorEvaluationMetadata { + // inputs_to_monitor: Vec::new(), + // context, + // custom_node_to_evaluate: None, + // }, + // Some(ExportConfig { + // file_name, + // file_type, + // size: scaled_size, + // }), + // ); + // } + + // Reset the scope nodes for hide artboards/hide_artboard name + // self.executor.submit_node_graph_compilation(CompilationRequest { + // network: document.network_interface.document_network().clone(), + // font_cache: self.persistent_data.font_cache.clone(), + // editor_metadata: EditorMetadata { + // #[cfg(any(feature = "resvg", feature = "vello"))] + // use_vello: preferences.use_vello().use_vello, + // #[cfg(not(any(feature = "resvg", feature = "vello")))] + // use_vello: false, + // hide_artboards: false, + // for_export: false, + // view_mode: document.view_mode, + // transform_to_viewport: true, + // }, + // }); } + PortfolioMessage::ToggleRulers => { if let Some(document) = self.active_document_mut() { document.rulers_visible = !document.rulers_visible; @@ -834,7 +1181,7 @@ impl MessageHandler> for Portfolio } PortfolioMessage::UpdateDocumentWidgets => { if let Some(document) = self.active_document() { - document.update_document_widgets(responses, animation.is_playing(), timing_information.animation_time); + document.update_document_widgets(responses, animation.is_playing(), animation_time); } } PortfolioMessage::UpdateOpenDocumentsList => { @@ -853,10 +1200,6 @@ impl MessageHandler> for Portfolio .collect::>(); responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents }); } - PortfolioMessage::UpdateVelloPreference => { - responses.add(NodeGraphMessage::RunDocumentGraph); - self.persistent_data.use_vello = preferences.use_vello; - } } } @@ -990,29 +1333,68 @@ impl PortfolioMessageHandler { /text>"# // It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed. .to_string(); - responses.add(Message::EndBuffer { - render_metadata: graphene_std::renderer::RenderMetadata::default(), - }); + responses.add(Message::ProcessQueue((graphene_std::renderer::EvaluationMetadata::default(), Vec::new()))); responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error }); } result } - /// Get the id of the node that should be used as the target for the spreadsheet - pub fn inspect_node_id(&self) -> Option { - // Spreadsheet not open, skipping - if !self.spreadsheet.spreadsheet_view_open { - return None; + // Returns an error if the data could not be introspected, returns None if the data type could not be rendered. + fn try_render_thumbnail(&self, protonode_input: &CompiledProtonodeInput) -> ThumbnailRenderResult { + let Ok(introspected_data) = self.introspected_input_data.get(protonode_input) else { + log::error!("Could not introspect node from input: {:?}", protonode_input); + return ThumbnailRenderResult::ClearThumbnail; + }; + + if let Some(previous_tagged_value) = self.downcasted_input_data.get(protonode_input) { + if previous_tagged_value.compare_value_to_dyn_any(introspected_data) { + return ThumbnailRenderResult::NoChange; + } } + + let Ok(new_tagged_value) = TaggedValue::try_from_std_any_ref(&introspected_data) else { + return ThumbnailRenderResult::ClearThumbnail; + }; + + + let Some(renderable_data) = TaggedValue::as_renderable(&new_tagged_value) else { + // New value is not renderable + return ThumbnailRenderResult::ClearThumbnail; + }; - let document = self.documents.get(&self.active_document_id?)?; - let selected_nodes = document.network_interface.selected_nodes().0; + let render_params = RenderParams { + view_mode: ViewMode::Normal, + culling_bounds: bounds, + thumbnail: true, + hide_artboards: false, + for_export: false, + for_mask: false, + alignment_parent_transform: None, + }; - // Selected nodes != 1, skipping - if selected_nodes.len() != 1 { - return None; - } + // Render the thumbnail data into an SVG string + let mut render = SvgRender::new(); + renderable_data.render_svg(&mut render, &render_params); - selected_nodes.first().copied() + // Give the SVG a viewbox and outer ... wrapper tag + let [min, max] = renderable_data.bounding_box(DAffine2::IDENTITY, true).unwrap_or_default(); + render.format_svg(min, max); + + self.downcasted_input_data.insert(protonode_input, new_tagged_value); + + ThumbnailRenderResult::UpdateThumbnail(render.svg.to_svg_string()) } } + +#[derive(Clone, Debug, Default)] +pub struct ThumbnailRenderResponse { + add: Vec<(SNI, String)>, + clear: Vec, +} + +pub enum ThumbnailRenderResult { + NoChange, + // Cleared if there is an error or the data could not be rendered + ClearThumbnail, + UpdateThumbnail(String), +} diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs index af579cc17f..2d2e37534d 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs @@ -1,5 +1,5 @@ -use crate::messages::prelude::*; -use crate::node_graph_executor::InspectResult; +use graph_craft::document::AbsoluteInputConnector; +use graphene_std::uuid::CompiledProtonodeInput; /// The spreadsheet UI allows for instance data to be previewed. #[impl_message(Message, PortfolioMessage, Spreadsheet)] @@ -7,27 +7,26 @@ use crate::node_graph_executor::InspectResult; pub enum SpreadsheetMessage { ToggleOpen, - UpdateLayout { - #[serde(skip)] - inspect_result: InspectResult, - }, + UpdateLayout { inpect_input: InspectInputConnector }, - PushToInstancePath { - index: usize, - }, - TruncateInstancePath { - len: usize, - }, + PushToInstancePath { index: usize }, + TruncateInstancePath { len: usize }, - ViewVectorDataDomain { - domain: VectorDataDomain, - }, + ViewVectorDataDomain { domain: VectorDataDomain }, } -#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)] +#[derive(PartialEq, Eq, Clone, Copy, Default, Debug)] pub enum VectorDataDomain { #[default] Points, Segments, Regions, } + +/// The mapping of input where the data is extracted from to the selected input to display data for +#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)] +// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] +pub struct InspectInputConnector { + pub input_connector: AbsoluteInputConnector, + pub protonode_input: CompiledProtonodeInput, +} diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index 52e8a76798..ec9c272899 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -1,63 +1,74 @@ use super::VectorDataDomain; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout}; +use crate::messages::portfolio::spreadsheet::InspectInputConnector; use crate::messages::prelude::*; use crate::messages::tool::tool_messages::tool_prelude::*; -use graph_craft::document::NodeId; +use graph_craft::document::{AbsoluteInputConnector, NodeId}; use graphene_std::Color; use graphene_std::Context; use graphene_std::GraphicGroupTable; use graphene_std::instances::Instances; use graphene_std::memo::IORecord; use graphene_std::raster::Image; +use graphene_std::uuid::CompiledProtonodeInput; use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement}; use std::any::Any; use std::sync::Arc; +pub struct SpreadsheetMessageHandlerData { + pub introspected_data: &HashMap>; +} + /// The spreadsheet UI allows for instance data to be previewed. #[derive(Default, Debug, Clone, ExtractField)] pub struct SpreadsheetMessageHandler { /// Sets whether or not the spreadsheet is drawn. pub spreadsheet_view_open: bool, - inspect_node: Option, - introspected_data: Option>, + inspect_input: Option, + // Downcasted data is not saved because the spreadsheet is simply a window into the data flowing through the input + // introspected_data: Option, instances_path: Vec, viewing_vector_data_domain: VectorDataDomain, } #[message_handler_data] -impl MessageHandler for SpreadsheetMessageHandler { - fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, _: ()) { +impl MessageHandler for SpreadsheetMessageHandler { + fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, data: SpreadsheetMessageHandlerData) { + let {introspected_data} = data; match message { SpreadsheetMessage::ToggleOpen => { self.spreadsheet_view_open = !self.spreadsheet_view_open; - // Run the graph to grab the data if self.spreadsheet_view_open { - responses.add(NodeGraphMessage::RunDocumentGraph); + // TODO: This will not get always get data since the input could be cached, and the monitor node would not + // Be run on the evaluation. To solve this, pass in an AbsoluteNodeInput as a parameter to the compilation which tells the compiler + // to generate a random SNI in order to reset any downstream cache + // Run the graph to grab the data + responses.add(PortfolioMessage::EvaluateActiveDocument); } // Update checked UI state for open responses.add(MenuBarMessage::SendLayout); self.update_layout(responses); } - SpreadsheetMessage::UpdateLayout { mut inspect_result } => { - self.inspect_node = Some(inspect_result.inspect_node); - self.introspected_data = inspect_result.take_data(); - self.update_layout(responses) + // Queued on introspection request, runs on introspection response when the data has been sent back to the editor + SpreadsheetMessage::UpdateLayout { inpect_input } => { + self.inspect_input = Some(inpect_input); + self.update_layout(introspected_data, responses); } SpreadsheetMessage::PushToInstancePath { index } => { self.instances_path.push(index); - self.update_layout(responses); + self.update_layout(introspected_data, responses); } SpreadsheetMessage::TruncateInstancePath { len } => { self.instances_path.truncate(len); - self.update_layout(responses); + self.update_layout(introspected_data, responses); } SpreadsheetMessage::ViewVectorDataDomain { domain } => { self.viewing_vector_data_domain = domain; - self.update_layout(responses); + self.update_layout(introspected_data, responses); } } } @@ -68,9 +79,10 @@ impl MessageHandler for SpreadsheetMessageHandler { } impl SpreadsheetMessageHandler { - fn update_layout(&mut self, responses: &mut VecDeque) { + fn update_layout(&mut self, introspected_data: &HashMap>, responses: &mut VecDeque) { responses.add(FrontendMessage::UpdateSpreadsheetState { - node: self.inspect_node, + // The node is sent when the data is available + node: None, open: self.spreadsheet_view_open, }); if !self.spreadsheet_view_open { @@ -82,12 +94,20 @@ impl SpreadsheetMessageHandler { breadcrumbs: Vec::new(), vector_data_domain: self.viewing_vector_data_domain, }; - let mut layout = self - .introspected_data - .as_ref() - .map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data)) - .unwrap_or_else(|| Some(label("No data"))) - .unwrap_or_else(|| label("Failed to downcast data")); + let mut layout = match self.inspect_input { + Some(inspect_input) => { + match introspected_data.get(&inspect_input.protonode_input){ + Some(data) => { + match generate_layout(instrospected_data, &mut layout_data) { + Some(layout) => layout, + None => label("The introspected data is not a supported type to be displayed."), + } + }, + None => label("Introspected data is not available for this input. This input may be cached."), + } + }, + None => label("No input selected to show data for."), + }; if layout_data.breadcrumbs.len() > 1 { let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs) @@ -110,21 +130,15 @@ struct LayoutData<'a> { vector_data_domain: VectorDataDomain, } -fn generate_layout(introspected_data: &Arc, data: &mut LayoutData) -> Option> { +fn generate_layout(introspected_data: &Box, data: &mut LayoutData) -> Option> { // We simply try random types. TODO: better strategy. #[allow(clippy::manual_map)] - if let Some(io) = introspected_data.downcast_ref::>() { - Some(io.output.layout_with_breadcrumb(data)) - } else if let Some(io) = introspected_data.downcast_ref::>() { - Some(io.output.layout_with_breadcrumb(data)) - } else if let Some(io) = introspected_data.downcast_ref::>() { - Some(io.output.layout_with_breadcrumb(data)) - } else if let Some(io) = introspected_data.downcast_ref::>() { - Some(io.output.layout_with_breadcrumb(data)) - } else if let Some(io) = introspected_data.downcast_ref::>() { - Some(io.output.layout_with_breadcrumb(data)) - } else if let Some(io) = introspected_data.downcast_ref::>() { - Some(io.output.layout_with_breadcrumb(data)) + if let Some(io) = introspected_data.downcast_ref::() { + Some(io.layout_with_breadcrumb(data)) + } else if let Some(io) = introspected_data.downcast_ref::() { + Some(io.layout_with_breadcrumb(data)) + } else if let Some(io) = introspected_data.downcast_ref::() { + Some(io.layout_with_breadcrumb(data)) } else { None } diff --git a/editor/src/messages/portfolio/utility_types.rs b/editor/src/messages/portfolio/utility_types.rs index fb0b2e4b4d..13cc7b9fec 100644 --- a/editor/src/messages/portfolio/utility_types.rs +++ b/editor/src/messages/portfolio/utility_types.rs @@ -2,8 +2,7 @@ use graphene_std::text::FontCache; #[derive(Debug, Default)] pub struct PersistentData { - pub font_cache: FontCache, - pub use_vello: bool, + pub font_cache: Arc, } #[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)] diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index a79ad379b2..8149959a61 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -53,8 +53,6 @@ impl MessageHandler for PreferencesMessageHandler { if let Ok(deserialized_preferences) = serde_json::from_str::(&preferences) { *self = deserialized_preferences; - responses.add(PortfolioMessage::EditorPreferences); - responses.add(PortfolioMessage::UpdateVelloPreference); responses.add(PreferencesMessage::ModifyLayout { zoom_with_scroll: self.zoom_with_scroll, }); @@ -70,8 +68,7 @@ impl MessageHandler for PreferencesMessageHandler { // Per-preference messages PreferencesMessage::UseVello { use_vello } => { self.use_vello = use_vello; - responses.add(PortfolioMessage::UpdateVelloPreference); - responses.add(PortfolioMessage::EditorPreferences); + responses.add(PortfolioMessage::CompileActiveDocument); } PreferencesMessage::VectorMeshes { enabled } => { self.vector_meshes = enabled; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs index 3995a1f401..3ff2f52c1d 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs @@ -204,6 +204,6 @@ impl NumberOfPointsDial { input_connector: InputConnector::node(node_id, 1), input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs index fc00e078cf..794131cfd0 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs @@ -450,6 +450,6 @@ impl PointRadiusHandle { input_connector: InputConnector::node(node_id, radius_index), input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } } diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 126e5b160c..cd56805974 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -13,6 +13,7 @@ use graphene_std::NodeInputDecleration; use graphene_std::raster::BlendMode; use graphene_std::raster_types::{CPU, GPU, RasterDataTable}; use graphene_std::text::{Font, TypesettingConfig}; +use graphene_std::uuid::NodeId; use graphene_std::vector::style::Gradient; use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType}; use std::collections::VecDeque; @@ -152,8 +153,8 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde parent: first_layer, }); - responses.add(NodeGraphMessage::RunDocumentGraph); - responses.add(Message::StartBuffer); + responses.add(PortfolioMessage::CompileActiveDocument); + responses.add(Message::StartQueue); responses.add(PenToolMessage::RecalculateLatestPointsPosition); } diff --git a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs index 985457c208..aed73cfa7f 100644 --- a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs @@ -78,7 +78,7 @@ impl Line { input_connector: InputConnector::node(node_id, 2), input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } pub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) { diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 202d026e0b..9446f9713f 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -378,8 +378,8 @@ impl Fsm for BrushToolFsmState { // Create the new layer, wait for the render output to return its transform, and then create the rest of the layer else { new_brush_layer(document, responses); - responses.add(NodeGraphMessage::RunDocumentGraph); - responses.add(Message::StartBuffer); + responses.add(PortfolioMessage::CompileActiveDocument); + responses.add(Message::StartQueue); responses.add(BrushToolMessage::DragStart); BrushToolFsmState::Ready } diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 8b0dbb73f3..c8a2ccbfcc 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -251,7 +251,7 @@ impl Fsm for FreehandToolFsmState { let nodes = vec![(NodeId(0), node)]; let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses); - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); tool_options.fill.apply_fill(layer, responses); tool_options.stroke.apply_stroke(tool_data.weight, layer, responses); tool_data.layer = Some(layer); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 215c90bdc4..74bc9e1991 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1258,7 +1258,7 @@ impl PenToolData { responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); // This causes the following message to be run only after the next graph evaluation runs and the transforms are updated - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); // It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky) responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport }); } @@ -2085,7 +2085,7 @@ impl Fsm for PenToolFsmState { node_ids: vec![layer.to_node()], delete_children: true, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } else if (latest_points && tool_data.prior_segment_endpoint.is_none()) || (tool_data.prior_segment_endpoint.is_some() && tool_data.prior_segment_layer != Some(layer) && latest_points) { @@ -2144,7 +2144,7 @@ impl Fsm for PenToolFsmState { node_ids: vec![layer.unwrap().to_node()], delete_children: true, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } responses.add(OverlaysMessage::Draw); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index f792ee2bb4..a5d704f5ad 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -517,7 +517,7 @@ impl SelectToolData { } let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect(); responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); self.layers_dragging = new_dragging; } @@ -555,7 +555,7 @@ impl SelectToolData { }) .collect(); responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SendGraph); self.layers_dragging = original; diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 0f43adf286..02da4bad33 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -491,7 +491,7 @@ impl Fsm for ShapeToolFsmState { input_connector: InputConnector::node(node_id, 1), input: NodeInput::value(TaggedValue::U32(n + 1), false), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } self @@ -520,7 +520,7 @@ impl Fsm for ShapeToolFsmState { input_connector: InputConnector::node(node_id, 1), input: NodeInput::value(TaggedValue::U32((n - 1).max(3)), false), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } self @@ -599,7 +599,7 @@ impl Fsm for ShapeToolFsmState { let nodes = vec![(NodeId(0), node)]; let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); match tool_data.current_shape { ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => { diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 0a96443ee5..af5504828e 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -360,7 +360,7 @@ impl Fsm for SplineToolFsmState { tool_options.stroke.apply_stroke(tool_data.weight, layer, responses); tool_data.current_layer = Some(layer); - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); SplineToolFsmState::Drawing } diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 0653bb83d8..ded042256e 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -298,7 +298,7 @@ impl TextToolData { node_ids: vec![self.layer.to_node()], delete_children: true, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); TextToolFsmState::Ready } @@ -362,7 +362,7 @@ impl TextToolData { input_connector: InputConnector::node(graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(), 1), input: NodeInput::value(TaggedValue::String("".to_string()), false), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); }; } @@ -381,7 +381,7 @@ impl TextToolData { parent: document.new_layer_parent(true), insert_index: 0, }); - responses.add(Message::StartBuffer); + responses.add(Message::StartQueue); responses.add(GraphOperationMessage::FillSet { layer: self.layer, fill: if editing_text.color.is_some() { @@ -402,7 +402,7 @@ impl TextToolData { responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option { @@ -649,7 +649,7 @@ impl Fsm for TextToolFsmState { skip_rerender: false, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); // Auto-panning let messages = [ @@ -710,7 +710,7 @@ impl Fsm for TextToolFsmState { transform_in: TransformIn::Viewport, skip_rerender: false, }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); // Auto-panning let messages = [ @@ -830,7 +830,7 @@ impl Fsm for TextToolFsmState { input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1), input: NodeInput::value(TaggedValue::String(tool_data.new_text.clone()), false), }); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); TextToolFsmState::Ready } else { diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index c89373f956..af1750debb 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -291,7 +291,7 @@ impl MessageHandler> for update_colinear_handles(&selected_layers, document, responses); responses.add(DocumentMessage::EndTransaction); responses.add(ToolMessage::UpdateHints); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::CompileActiveDocument); } if using_path_tool { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 8c6a7d13a3..2e3bc5057b 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -1,71 +1,94 @@ +use std::sync::Arc; + use crate::consts::FILE_SAVE_SUFFIX; use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::prelude::*; -use glam::{DAffine2, DVec2, UVec2}; -use graph_craft::document::value::{RenderOutput, TaggedValue}; -use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, generate_uuid}; +use dyn_any::DynAny; +use glam::DAffine2; +use graph_craft::document::value::{NetworkOutput, TaggedValue}; +use graph_craft::document::{ + AbsoluteInputConnector, AbsoluteOutputConnector, CompilationMetadata, CompiledNodeMetadata, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, generate_uuid, +}; use graph_craft::proto::GraphErrors; -use graph_craft::wasm_application_io::EditorPreferences; -use graphene_std::application_io::TimingInformation; -use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig}; -use graphene_std::renderer::RenderSvgSegmentList; -use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender}; -use graphene_std::renderer::{RenderMetadata, format_transform_matrix}; +use graph_craft::wasm_application_io::{EditorCompilationMetadata, EditorEvaluationMetadata, EditorMetadata}; +use graphene_std::application_io::{CompilationMetadata, TimingInformation}; +use graphene_std::application_io::{EditorEvaluationMetadata, NodeGraphUpdateMessage}; +use graphene_std::memo::IntrospectMode; +use graphene_std::renderer::{EvaluationMetadata, format_transform_matrix}; +use graphene_std::renderer::{RenderMetadata, RenderSvgSegmentList}; +use graphene_std::renderer::{RenderParams, SvgRender}; use graphene_std::text::FontCache; -use graphene_std::transform::Footprint; +use graphene_std::transform::{Footprint, RenderQuality}; +use graphene_std::uuid::{CompiledProtonodeInput, ProtonodePath, SNI}; use graphene_std::vector::VectorData; use graphene_std::vector::style::ViewMode; -use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta; +use graphene_std::wasm_application_io::NetworkOutput; +use graphene_std::{CompiledProtonodeInput, OwnedContextImpl, SNI}; mod runtime_io; +use interpreted_executor::dynamic_executor::{EditorContext, ResolvedDocumentNodeMetadata}; pub use runtime_io::NodeRuntimeIO; mod runtime; pub use runtime::*; -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct ExecutionRequest { - execution_id: u64, - render_config: RenderConfig, +#[derive(Clone, Debug, Default, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +pub struct CompilationRequest { + pub network: NodeNetwork, + // Data which is avaialable from scope inputs (currently WasmEditorApi, but will be split) + pub font_cache: Arc, + pub editor_metadata: EditorMetadata, } -#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] -pub struct ExecutionResponse { - execution_id: u64, - result: Result, - responses: VecDeque, - transform: DAffine2, - vector_modify: HashMap, - /// The resulting value from the temporary inspected during execution - inspect_result: Option, -} - -#[derive(serde::Serialize, serde::Deserialize)] pub struct CompilationResponse { - result: Result, + result: Result, node_graph_errors: GraphErrors, } -#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] +// Metadata the editor sends when evaluating the network +#[derive(Debug, Default, DynAny)] +pub struct EvaluationRequest { + pub evaluation_id: u64, + pub inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>, + pub context: EditorContext, + // pub custom_node_to_evaluate: Option, +} + +// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] +pub struct EvaluationResponse { + evaluation_id: u64, + result: Result, + introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box)>, + // TODO: Handle transforming node graph output in the node graph itself + transform: DAffine2, +} + +// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] pub enum NodeGraphUpdate { - ExecutionResponse(ExecutionResponse), CompilationResponse(CompilationResponse), - NodeGraphUpdateMessage(NodeGraphUpdateMessage), + EvaluationResponse(EvaluationResponse), } #[derive(Debug, Default)] pub struct NodeGraphExecutor { runtime_io: NodeRuntimeIO, - futures: HashMap, - node_graph_hash: u64, - old_inspect_node: Option, + futures: HashMap, } #[derive(Debug, Clone)] -struct ExecutionContext { +struct EvaluationContext { export_config: Option, } +impl Default for NodeGraphExecutor { + fn default() -> Self { + Self { + futures: Default::default(), + runtime_io: NodeRuntimeIO::new(), + } + } +} + impl NodeGraphExecutor { /// A local runtime is useful on threads since having global state causes flakes #[cfg(test)] @@ -75,189 +98,54 @@ impl NodeGraphExecutor { let node_runtime = NodeRuntime::new(request_receiver, response_sender); let node_executor = Self { - futures: Default::default(), + futures: HashMap::new(), runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver), - node_graph_hash: 0, - old_inspect_node: None, }; (node_runtime, node_executor) } - /// Execute the network by flattening it and creating a borrow stack. - fn queue_execution(&self, render_config: RenderConfig) -> u64 { - let execution_id = generate_uuid(); - let request = ExecutionRequest { execution_id, render_config }; - self.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)).expect("Failed to send generation request"); - - execution_id - } - - pub fn update_font_cache(&self, font_cache: FontCache) { - self.runtime_io.send(GraphRuntimeRequest::FontCacheUpdate(font_cache)).expect("Failed to send font cache update"); - } - - pub fn update_editor_preferences(&self, editor_preferences: EditorPreferences) { - self.runtime_io - .send(GraphRuntimeRequest::EditorPreferencesUpdate(editor_preferences)) - .expect("Failed to send editor preferences"); - } /// Updates the network to monitor all inputs. Useful for the testing. #[cfg(test)] pub(crate) fn update_node_graph_instrumented(&mut self, document: &mut DocumentMessageHandler) -> Result { - // We should always invalidate the cache. - self.node_graph_hash = generate_uuid(); let mut network = document.network_interface.document_network().clone(); let instrumented = Instrumented::new(&mut network); self.runtime_io - .send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node: None })) + .send(GraphRuntimeRequest::CompilationRequest(CompilationRequest { network, ..Default::default() })) .map_err(|e| e.to_string())?; Ok(instrumented) } - /// Update the cached network if necessary. - fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, inspect_node: Option, ignore_hash: bool) -> Result<(), String> { - let network_hash = document.network_interface.document_network().current_hash(); - // Refresh the graph when it changes or the inspect node changes - if network_hash != self.node_graph_hash || self.old_inspect_node != inspect_node || ignore_hash { - let network = document.network_interface.document_network().clone(); - self.old_inspect_node = inspect_node; - self.node_graph_hash = network_hash; - - self.runtime_io - .send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node })) - .map_err(|e| e.to_string())?; - } - Ok(()) + /// Compile the network + pub fn submit_node_graph_compilation(&mut self, compilation_request: CompilationRequest) { + self.runtime_io.send(GraphRuntimeRequest::CompilationRequest(compilation_request)).map_err(|e| e.to_string()); } /// Adds an evaluate request for whatever current network is cached. - pub(crate) fn submit_current_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2, time: TimingInformation) -> Result<(), String> { - let render_config = RenderConfig { - viewport: Footprint { - transform: document.metadata().document_to_viewport, - resolution: viewport_resolution, - ..Default::default() - }, - time, - #[cfg(any(feature = "resvg", feature = "vello"))] - export_format: graphene_std::application_io::ExportFormat::Canvas, - #[cfg(not(any(feature = "resvg", feature = "vello")))] - export_format: graphene_std::application_io::ExportFormat::Svg, - view_mode: document.view_mode, - hide_artboards: false, - for_export: false, - }; - - // Execute the node graph - let execution_id = self.queue_execution(render_config); - - self.futures.insert(execution_id, ExecutionContext { export_config: None }); - - Ok(()) - } - - /// Evaluates a node graph, computing the entire graph pub fn submit_node_graph_evaluation( &mut self, - document: &mut DocumentMessageHandler, - viewport_resolution: UVec2, - time: TimingInformation, - inspect_node: Option, - ignore_hash: bool, - ) -> Result<(), String> { - self.update_node_graph(document, inspect_node, ignore_hash)?; - self.submit_current_node_graph_evaluation(document, viewport_resolution, time)?; - - Ok(()) - } - - /// Evaluates a node graph for export - pub fn submit_document_export(&mut self, document: &mut DocumentMessageHandler, mut export_config: ExportConfig) -> Result<(), String> { - let network = document.network_interface.document_network().clone(); - - // Calculate the bounding box of the region to be exported - let bounds = match export_config.bounds { - ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!export_config.transparent_background), - ExportBounds::Selection => document.network_interface.selected_bounds_document_space(!export_config.transparent_background, &[]), - ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id), - } - .ok_or_else(|| "No bounding box".to_string())?; - let size = bounds[1] - bounds[0]; - let transform = DAffine2::from_translation(bounds[0]).inverse(); - - let render_config = RenderConfig { - viewport: Footprint { - transform: DAffine2::from_scale(DVec2::splat(export_config.scale_factor)) * transform, - resolution: (size * export_config.scale_factor).as_uvec2(), - ..Default::default() - }, - time: Default::default(), - export_format: graphene_std::application_io::ExportFormat::Svg, - view_mode: document.view_mode, - hide_artboards: export_config.transparent_background, - for_export: true, - }; - export_config.size = size; - - // Execute the node graph - self.runtime_io - .send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node: None })) - .map_err(|e| e.to_string())?; - let execution_id = self.queue_execution(render_config); - let execution_context = ExecutionContext { export_config: Some(export_config) }; - self.futures.insert(execution_id, execution_context); - - Ok(()) - } - - fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque) -> Result<(), String> { - let TaggedValue::RenderOutput(RenderOutput { - data: graphene_std::wasm_application_io::RenderOutputType::Svg(svg), - .. - }) = node_graph_output - else { - return Err("Incorrect render type for exporting (expected RenderOutput::Svg)".to_string()); - }; - - let ExportConfig { - file_type, - file_name, - size, - scale_factor, - .. - } = export_config; - - let file_suffix = &format!(".{file_type:?}").to_lowercase(); - let name = match file_name.ends_with(FILE_SAVE_SUFFIX) { - true => file_name.replace(FILE_SAVE_SUFFIX, file_suffix), - false => file_name + file_suffix, - }; - - if file_type == FileType::Svg { - responses.add(FrontendMessage::TriggerDownloadTextFile { document: svg, name }); - } else { - let mime = file_type.to_mime().to_string(); - let size = (size * scale_factor).into(); - responses.add(FrontendMessage::TriggerDownloadImage { svg, name, mime, size }); - } - Ok(()) + context: EditorContext, + inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>, + custom_node_to_evaluate: Option, + export_config: Option, + ) { + let evaluation_id = generate_uuid(); + self.runtime_io.send(GraphRuntimeRequest::EvaluationRequest(editor_evaluation_request)).map_err(|e| e.to_string()); + let evaluation_context = EvaluationContext { export_config }; + self.futures.insert(evaluation_id, evaluation_context); } + // Continuously poll the executor (called by request animation frame) pub fn poll_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque) -> Result<(), String> { - let results = self.runtime_io.receive().collect::>(); - for response in results { + // Moved into portfolio message handler, since this is where the introspected inputs are saved + for response in self.runtime_io.receive() { match response { - NodeGraphUpdate::ExecutionResponse(execution_response) => { - let ExecutionResponse { - execution_id, - result, - responses: existing_responses, - transform, - vector_modify, - inspect_result, - } = execution_response; - + NodeGraphUpdate::EvaluationResponse(EvaluationResponse { + evaluation_id, + result, + transform, + introspected_inputs, + }) => { responses.add(OverlaysMessage::Draw); let node_graph_output = match result { @@ -269,51 +157,62 @@ impl NodeGraphExecutor { return Err(format!("Node graph evaluation failed:\n{e}")); } }; - - responses.extend(existing_responses.into_iter().map(Into::into)); - document.network_interface.update_vector_modify(vector_modify); - - let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?; - if let Some(export_config) = execution_context.export_config { - // Special handling for exporting the artwork - self.export(node_graph_output, export_config, responses)? - } else { - self.process_node_graph_output(node_graph_output, transform, responses)? - } - - // Update the spreadsheet on the frontend using the value of the inspect result. - if self.old_inspect_node.is_some() { - if let Some(inspect_result) = inspect_result { - responses.add(SpreadsheetMessage::UpdateLayout { inspect_result }); + let render_output = match node_graph_output { + TaggedValue::RenderOutput(render_output) => render_output, + value => { + return Err("Incorrect render type for exporting (expected NetworkOutput)".to_string()); } + }; + + let evaluation_context = self.futures.remove(&evaluation_id).ok_or_else(|| "Invalid generation ID".to_string())?; + if let Some(export_config) = evaluation_context.export_config { + // Export + let TaggedValue::RenderOutput(RenderOutput { + data: graphene_std::wasm_application_io::RenderOutputType::Svg(svg), + .. + }) = node_graph_output + else { + return Err("Incorrect render type for exporting (expected RenderOutput::Svg)".to_string()); + }; + + match export_config.file_type { + FileType::Svg => { + responses.add(FrontendMessage::TriggerDownloadTextFile { + document: svg, + name: export_config.file_name, + }); + } + _ => { + responses.add(FrontendMessage::TriggerDownloadImage { + svg, + name: export_config.file_name, + mime: export_config.file_type.to_mime().to_string(), + size: export_config.size.into(), + }); + } + } + } else { + // Update artwork + self.process_node_graph_output(render_output, introspected_inputs, transform, responses); } } - NodeGraphUpdate::CompilationResponse(execution_response) => { - let CompilationResponse { node_graph_errors, result } = execution_response; - let type_delta = match result { + NodeGraphUpdate::CompilationResponse(compilation_response) => { + let CompilationResponse { node_graph_errors, result } = compilation_response; + let compilation_metadata = match result { Err(e) => { // Clear the click targets while the graph is in an un-renderable state - document.network_interface.update_click_targets(HashMap::new()); document.network_interface.update_vector_modify(HashMap::new()); - log::trace!("{e}"); - - responses.add(NodeGraphMessage::UpdateTypes { - resolved_types: Default::default(), - node_graph_errors, - }); + document.node_graph_handler.node_graph_errors = node_graph_errors; responses.add(NodeGraphMessage::SendGraph); + log::trace!("{e}"); return Err(format!("Node graph evaluation failed:\n{e}")); } Ok(result) => result, }; - - responses.add(NodeGraphMessage::UpdateTypes { - resolved_types: type_delta, - node_graph_errors, - }); + responses.add(PortfolioMessage::ProcessCompilationResponse { compilation_metadata }); responses.add(NodeGraphMessage::SendGraph); } } @@ -321,31 +220,13 @@ impl NodeGraphExecutor { Ok(()) } - fn debug_render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque) { - // Setup rendering - let mut render = SvgRender::new(); - let render_params = RenderParams { - view_mode: ViewMode::Normal, - culling_bounds: None, - thumbnail: false, - hide_artboards: false, - for_export: false, - for_mask: false, - alignment_parent_transform: None, - }; - - // Render SVG - render_object.render_svg(&mut render, &render_params); - - // Concatenate the defs and the SVG into one string - render.wrap_with_transform(transform, None); - let svg = render.svg.to_svg_string(); - - // Send to frontend - responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); - } - - fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque) -> Result<(), String> { + fn process_node_graph_output( + &mut self, + node_graph_output: TaggedValue, + introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box)>, + transform: DAffine2, + responses: &mut VecDeque, + ) -> Result<(), String> { let mut render_output_metadata = RenderMetadata::default(); match node_graph_output { TaggedValue::RenderOutput(render_output) => { @@ -370,149 +251,156 @@ impl NodeGraphExecutor { render_output_metadata = render_output.metadata; } - TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses), - TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses), - TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses), - TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses), - TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses), - TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses), - TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses), - TaggedValue::RasterData(render_object) => Self::debug_render(render_object, transform, responses), - TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::RasterData(render_object) => Self::debug_render(render_object, transform, responses), + // TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses), _ => { return Err(format!("Invalid node graph output type: {node_graph_output:#?}")); } }; - responses.add(Message::EndBuffer { - render_metadata: render_output_metadata, - }); - responses.add(DocumentMessage::RenderScrollbars); - responses.add(DocumentMessage::RenderRulers); - responses.add(OverlaysMessage::Draw); + responses.add(Message::ProcessQueue((render_output_metadata, introspected_inputs))); Ok(()) } } +// pub enum AnimationState { +// #[default] +// Stopped, +// Playing { +// start: f64, +// }, +// Paused { +// start: f64, +// pause_time: f64, +// }, +// } + // Re-export for usage by tests in other modules -#[cfg(test)] -pub use test::Instrumented; +// #[cfg(test)] +// pub use test::Instrumented; -#[cfg(test)] -mod test { - use std::sync::Arc; +// #[cfg(test)] +// mod test { +// use std::sync::Arc; - use super::*; - use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; - use crate::test_utils::test_prelude::{self, NodeGraphLayer}; - use graph_craft::ProtoNodeIdentifier; - use graph_craft::document::NodeNetwork; - use graphene_std::Context; - use graphene_std::NodeInputDecleration; - use graphene_std::memo::IORecord; - use test_prelude::LayerNodeIdentifier; +// use super::*; +// use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; +// use crate::test_utils::test_prelude::{self, NodeGraphLayer}; +// use graph_craft::ProtoNodeIdentifier; +// use graph_craft::document::NodeNetwork; +// use graphene_std::Context; +// use graphene_std::NodeInputDecleration; +// use graphene_std::memo::IORecord; +// use test_prelude::LayerNodeIdentifier; - /// Stores all of the monitor nodes that have been attached to a graph - #[derive(Default)] - pub struct Instrumented { - protonodes_by_name: HashMap>>>, - protonodes_by_path: HashMap, Vec>>, - } +// /// Stores all of the monitor nodes that have been attached to a graph +// #[derive(Default)] +// pub struct Instrumented { +// protonodes_by_name: HashMap>>>, +// protonodes_by_path: HashMap, Vec>>, +// } - impl Instrumented { - /// Adds montior nodes to the network - fn add(&mut self, network: &mut NodeNetwork, path: &mut Vec) { - // Required to do seperately to satiate the borrow checker. - let mut monitor_nodes = Vec::new(); - for (id, node) in network.nodes.iter_mut() { - // Recursively instrument - if let DocumentNodeImplementation::Network(nested) = &mut node.implementation { - path.push(*id); - self.add(nested, path); - path.pop(); - } - let mut monitor_node_ids = Vec::with_capacity(node.inputs.len()); - for input in &mut node.inputs { - let node_id = NodeId::new(); - let old_input = std::mem::replace(input, NodeInput::node(node_id, 0)); - monitor_nodes.push((old_input, node_id)); - path.push(node_id); - monitor_node_ids.push(path.clone()); - path.pop(); - } - if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation { - path.push(*id); - self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone()); - self.protonodes_by_path.insert(path.clone(), monitor_node_ids); - path.pop(); - } - } - for (input, monitor_id) in monitor_nodes { - let monitor_node = DocumentNode { - inputs: vec![input], - implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), - manual_composition: Some(graph_craft::generic!(T)), - skip_deduplication: true, - ..Default::default() - }; - network.nodes.insert(monitor_id, monitor_node); - } - } +// impl Instrumented { +// /// Adds montior nodes to the network +// fn add(&mut self, network: &mut NodeNetwork, path: &mut Vec) { +// // Required to do seperately to satiate the borrow checker. +// let mut monitor_nodes = Vec::new(); +// for (id, node) in network.nodes.iter_mut() { +// // Recursively instrument +// if let DocumentNodeImplementation::Network(nested) = &mut node.implementation { +// path.push(*id); +// self.add(nested, path); +// path.pop(); +// } +// let mut monitor_node_ids = Vec::with_capacity(node.inputs.len()); +// for input in &mut node.inputs { +// let node_id = NodeId::new(); +// let old_input = std::mem::replace(input, NodeInput::node(node_id, 0)); +// monitor_nodes.push((old_input, node_id)); +// path.push(node_id); +// monitor_node_ids.push(path.clone()); +// path.pop(); +// } +// if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation { +// path.push(*id); +// self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone()); +// self.protonodes_by_path.insert(path.clone(), monitor_node_ids); +// path.pop(); +// } +// } +// for (input, monitor_id) in monitor_nodes { +// let monitor_node = DocumentNode { +// inputs: vec![input], +// implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), +// manual_composition: Some(graph_craft::generic!(T)), +// skip_deduplication: true, +// ..Default::default() +// }; +// network.nodes.insert(monitor_id, monitor_node); +// } +// } - /// Instrument a graph and return a new [Instrumented] state. - pub fn new(network: &mut NodeNetwork) -> Self { - let mut instrumented = Self::default(); - instrumented.add(network, &mut Vec::new()); - instrumented - } +// /// Instrument a graph and return a new [Instrumented] state. +// pub fn new(network: &mut NodeNetwork) -> Self { +// let mut instrumented = Self::default(); +// instrumented.add(network, &mut Vec::new()); +// instrumented +// } - fn downcast(dynamic: Arc) -> Option - where - Input::Result: Send + Sync + Clone + 'static, - { - // This is quite inflexible since it only allows the footprint as inputs. - if let Some(x) = dynamic.downcast_ref::>() { - Some(x.output.clone()) - } else if let Some(x) = dynamic.downcast_ref::>() { - Some(x.output.clone()) - } else if let Some(x) = dynamic.downcast_ref::>() { - Some(x.output.clone()) - } else { - panic!("cannot downcast type for introspection"); - } - } +// fn downcast(dynamic: Arc) -> Option +// where +// Input::Result: Send + Sync + Clone + 'static, +// { +// // This is quite inflexible since it only allows the footprint as inputs. +// if let Some(x) = dynamic.downcast_ref::>() { +// Some(x.output.clone()) +// } else if let Some(x) = dynamic.downcast_ref::>() { +// Some(x.output.clone()) +// } else if let Some(x) = dynamic.downcast_ref::>() { +// Some(x.output.clone()) +// } else { +// panic!("cannot downcast type for introspection"); +// } +// } - /// Grab all of the values of the input every time it occurs in the graph. - pub fn grab_all_input<'a, Input: NodeInputDecleration + 'a>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator + 'a - where - Input::Result: Send + Sync + Clone + 'static, - { - self.protonodes_by_name - .get(&Input::identifier()) - .map_or([].as_slice(), |x| x.as_slice()) - .iter() - .filter_map(|inputs| inputs.get(Input::INDEX)) - .filter_map(|input_monitor_node| runtime.executor.introspect(input_monitor_node).ok()) - .filter_map(Instrumented::downcast::) - } +// /// Grab all of the values of the input every time it occurs in the graph. +// pub fn grab_all_input<'a, Input: NodeInputDecleration + 'a>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator + 'a +// where +// Input::Result: Send + Sync + Clone + 'static, +// { +// self.protonodes_by_name +// .get(&Input::identifier()) +// .map_or([].as_slice(), |x| x.as_slice()) +// .iter() +// .filter_map(|inputs| inputs.get(Input::INDEX)) +// .filter_map(|input_monitor_node| runtime.executor.introspect(input_monitor_node).ok()) +// .filter_map(Instrumented::downcast::) +// } - pub fn grab_protonode_input(&self, path: &Vec, runtime: &NodeRuntime) -> Option - where - Input::Result: Send + Sync + Clone + 'static, - { - let input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?; +// pub fn grab_protonode_input(&self, path: &Vec, runtime: &NodeRuntime) -> Option +// where +// Input::Result: Send + Sync + Clone + 'static, +// { +// let input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?; - let dynamic = runtime.executor.introspect(input_monitor_node).ok()?; +// let dynamic = runtime.executor.introspect(input_monitor_node).ok()?; - Self::downcast::(dynamic) - } +// Self::downcast::(dynamic) +// } - pub fn grab_input_from_layer(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, runtime: &NodeRuntime) -> Option - where - Input::Result: Send + Sync + Clone + 'static, - { - let node_graph_layer = NodeGraphLayer::new(layer, network_interface); - let node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?; - self.grab_protonode_input::(&vec![node], runtime) - } - } -} +// pub fn grab_input_from_layer(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, runtime: &NodeRuntime) -> Option +// where +// Input::Result: Send + Sync + Clone + 'static, +// { +// let node_graph_layer = NodeGraphLayer::new(layer, network_interface); +// let node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?; +// self.grab_protonode_input::(&vec![node], runtime) +// } +// } +// } diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index da92ad313b..8b119badb9 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -14,6 +14,7 @@ use graphene_std::memo::IORecord; use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender}; use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment}; use graphene_std::text::FontCache; +use graphene_std::uuid::{CompiledProtonodeInput, NodeId}; use graphene_std::vector::style::ViewMode; use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; @@ -24,8 +25,9 @@ use spin::Mutex; use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; -/// Persistent data between graph executions. It's updated via message passing from the editor thread with [`GraphRuntimeRequest`]`. -/// Some of these fields are put into a [`WasmEditorApi`] which is passed to the final compiled graph network upon each execution. +/// Persistent data between graph evaluations. It's updated via message passing from the editor thread with [`GraphRuntimeRequest`]`. +/// [`PortfolioMessage::CompileActiveDocument`] and [`PortfolioMessage::RenderActiveDocument`] are the two main entry points +/// Some of these fields are inserted into the network at compile time using the scope system /// Once the implementation is finished, this will live in a separate thread. Right now it's part of the main JS thread, but its own separate JS stack frame independent from the editor. pub struct NodeRuntime { #[cfg(test)] @@ -33,14 +35,11 @@ pub struct NodeRuntime { #[cfg(not(test))] executor: DynamicExecutor, receiver: Receiver, - sender: InternalNodeGraphUpdateSender, - editor_preferences: EditorPreferences, - old_graph: Option, - update_thumbnails: bool, + sender: NodeGraphRuntimeSender, + + application_io: Option>, - editor_api: Arc, node_graph_errors: GraphErrors, - monitor_nodes: Vec>, /// Which node is inspected and which monitor node is used (if any) for the current execution inspect_state: Option, @@ -48,26 +47,24 @@ pub struct NodeRuntime { /// Mapping of the fully-qualified node paths to their preprocessor substitutions. substitutions: HashMap, - // TODO: Remove, it doesn't need to be persisted anymore - /// The current renders of the thumbnails for layer nodes. - thumbnail_renders: HashMap>, - vector_modify: HashMap, + /// Stored in order to check for changes before sending to the frontend. + thumbnail_render_tagged_values: HashMap, } /// Messages passed from the editor thread to the node runtime thread. #[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum GraphRuntimeRequest { - GraphUpdate(GraphUpdate), - ExecutionRequest(ExecutionRequest), - FontCacheUpdate(FontCache), - EditorPreferencesUpdate(EditorPreferences), -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct GraphUpdate { - pub(super) network: NodeNetwork, - /// The node that should be temporary inspected during execution - pub(super) inspect_node: Option, + CompilationRequest(CompilationRequest), + // Makes a request to evaluate the network and stores data for the list of output connectors + // Should only monitor data for nodes which need their thumbnails. + EvaluationRequest(EvaluationRequest), + // Renders thumbnails for the data from the last execution + // If the upstream node stores data for the context override, then another evaluation must be performed at the input + // This is performed separately from execution requests, since thumbnails for animation should be updated once every 50ms or so. + ThumbnailRenderRequest(HashSet), + // Request the data from a list of node inputs. For example, used by vector modify to get the data at the input of every Path node. + // Can also be used by the spreadsheet/introspection system + IntrospectionRequest(HashSet<(CompiledProtonodeInput, IntrospectMode)>), } #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -81,21 +78,14 @@ pub struct ExportConfig { } #[derive(Clone)] -struct InternalNodeGraphUpdateSender(Sender); +struct NodeGraphRuntimeSender(Sender); -impl InternalNodeGraphUpdateSender { - fn send_generation_response(&self, response: CompilationResponse) { - self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send response") +impl NodeGraphRuntimeSender { + fn send_compilation_response(&self, response: CompilationResponse) { + self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send compilation response") } - - fn send_execution_response(&self, response: ExecutionResponse) { - self.0.send(NodeGraphUpdate::ExecutionResponse(response)).expect("Failed to send response") - } -} - -impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender { - fn send(&self, message: NodeGraphUpdateMessage) { - self.0.send(NodeGraphUpdate::NodeGraphUpdateMessage(message)).expect("Failed to send response") + fn send_evaluation_response(&self, response: EvaluationResponse) { + self.0.send(NodeGraphUpdate::EvaluationResponse(response)).expect("Failed to send evaluation response") } } @@ -106,151 +96,135 @@ impl NodeRuntime { Self { executor: DynamicExecutor::default(), receiver, - sender: InternalNodeGraphUpdateSender(sender.clone()), - editor_preferences: EditorPreferences::default(), - old_graph: None, - update_thumbnails: true, + sender: NodeGraphRuntimeSender(sender.clone()), - editor_api: WasmEditorApi { - font_cache: FontCache::default(), - editor_preferences: Box::new(EditorPreferences::default()), - node_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)), - - application_io: None, - } - .into(), + application_io: None, node_graph_errors: Vec::new(), - monitor_nodes: Vec::new(), substitutions: preprocessor::generate_node_substitutions(), - - thumbnail_renders: Default::default(), - vector_modify: Default::default(), + thumbnail_render_tagged_values: HashSet::new(), inspect_state: None, } } pub async fn run(&mut self) { - if self.editor_api.application_io.is_none() { - self.editor_api = WasmEditorApi { - #[cfg(not(test))] - application_io: Some(WasmApplicationIo::new().await.into()), - #[cfg(test)] - application_io: Some(WasmApplicationIo::new_offscreen().await.into()), - font_cache: self.editor_api.font_cache.clone(), - node_graph_message_sender: Box::new(self.sender.clone()), - editor_preferences: Box::new(self.editor_preferences.clone()), - } - .into(); + if self.application_io.is_none() { + #[cfg(not(test))] + self.application_io = Some(Arc::new(WasmApplicationIo::new().await)); + #[cfg(test)] + self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await)); } - let mut font = None; - let mut preferences = None; - let mut graph = None; - let mut execution = None; + // TODO: This deduplication of messages will probably cause more issues than it solved + // let mut graph = None; + // let mut execution = None; + // let mut thumbnails = None; + // let mut introspection = None; + // for request in self.receiver.try_iter() { + // match request { + // GraphRuntimeRequest::CompilationRequest(_) => graph = Some(request), + // GraphRuntimeRequest::EvaluationRequest(_) => execution = Some(request), + // GraphRuntimeRequest::ThumbnailRenderResponse(_) => thumbnails = Some(request), + // GraphRuntimeRequest::IntrospectionResponse(_) => introspection = Some(request), + // } + // } + // let requests = [font, preferences, graph, execution].into_iter().flatten(); + for request in self.receiver.try_iter() { match request { - GraphRuntimeRequest::GraphUpdate(_) => graph = Some(request), - GraphRuntimeRequest::ExecutionRequest(_) => execution = Some(request), - GraphRuntimeRequest::FontCacheUpdate(_) => font = Some(request), - GraphRuntimeRequest::EditorPreferencesUpdate(_) => preferences = Some(request), - } - } - let requests = [font, preferences, graph, execution].into_iter().flatten(); - - for request in requests { - match request { - GraphRuntimeRequest::FontCacheUpdate(font_cache) => { - self.editor_api = WasmEditorApi { - font_cache, - application_io: self.editor_api.application_io.clone(), - node_graph_message_sender: Box::new(self.sender.clone()), - editor_preferences: Box::new(self.editor_preferences.clone()), - } - .into(); - if let Some(graph) = self.old_graph.clone() { - // We ignore this result as compilation errors should have been reported in an earlier iteration - let _ = self.update_network(graph).await; - } - } - GraphRuntimeRequest::EditorPreferencesUpdate(preferences) => { - self.editor_preferences = preferences.clone(); - self.editor_api = WasmEditorApi { - font_cache: self.editor_api.font_cache.clone(), - application_io: self.editor_api.application_io.clone(), - node_graph_message_sender: Box::new(self.sender.clone()), - editor_preferences: Box::new(preferences), - } - .into(); - if let Some(graph) = self.old_graph.clone() { - // We ignore this result as compilation errors should have been reported in an earlier iteration - let _ = self.update_network(graph).await; - } - } - GraphRuntimeRequest::GraphUpdate(GraphUpdate { mut network, inspect_node }) => { + GraphRuntimeRequest::CompilationRequest(CompilationRequest { + mut network, + font_cache, + editor_metadata, + }) => { // Insert the monitor node to manage the inspection - self.inspect_state = inspect_node.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect)); + // self.inspect_state = inspect_node.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect)); - self.old_graph = Some(network.clone()); self.node_graph_errors.clear(); let result = self.update_network(network).await; - self.update_thumbnails = true; - self.sender.send_generation_response(CompilationResponse { + self.sender.send_compilation_response(CompilationResponse { result, node_graph_errors: self.node_graph_errors.clone(), }); } - GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => { - let transform = render_config.viewport.transform; + GraphRuntimeRequest::EvaluationRequest(EvaluationRequest { + evaluation_id, + context, + inputs_to_monitor, + // custom_node_to_evaluate + }) => { + for (protonode_input, introspect_mode) in inputs_to_monitor { + self.executor.set_introspect(protonode_input, introspect_mode) + } + let transform = context.render_config.viewport.transform; let result = self.execute_network(render_config).await; - let mut responses = VecDeque::new(); - // TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes - self.process_monitor_nodes(&mut responses, self.update_thumbnails); - self.update_thumbnails = false; - // Resolve the result from the inspection by accessing the monitor node - let inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor)); + let introspected_inputs = Vec::new(); + for (protonode_input, mode) in inputs_to_introspect { + let Ok(introspected_data) = self.executor.introspect(protonode_input, mode) else { + log::error!("Could not introspect node from input: {:?}", protonode_input); + continue; + }; + introspected_inputs.push((protonode_input, mode, introspected_data)); + } - self.sender.send_execution_response(ExecutionResponse { - execution_id, + self.sender.send_evaluation_response(EvaluationResponse { + evaluation_id, result, - responses, transform, - vector_modify: self.vector_modify.clone(), - inspect_result, + introspected_inputs, }); } + GraphRuntimeRequest::ThumbnailRenderRequest(input_to_render) => { + let mut thumbnail_response = ThumbnailRenderResponse::default(); + for input in input_to_render {} + self.sender.send_thumbnail_render_response(thumbnail_response); + } + GraphRuntimeRequest::IntrospectionRequest(inputs_to_introspect) => { + self.sender.send_introspection_response(introspection_response); + } } } } - async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { + async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { preprocessor::expand_network(&mut graph, &self.substitutions); + // Creates a network where the node paths to the document network are prefixed with NodeId(0) let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone()); // We assume only one output assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled"); - let c = Compiler {}; - let proto_network = match c.compile_single(scoped_network) { + // Modifies the NodeNetwork so the tagged values are removed and the document nodes with protonode implementations have their protonode ids set + // Needs to return a mapping of absolute input connectors to protonode callers, types for protonodes, and callers for protonodes, add/remove delta for resolved types + let (proto_network, protonode_callers_for_value, protonode_callers_for_node) = match scoped_network.flatten() { Ok(network) => network, - Err(e) => return Err(e), + Err(e) => { + log::error!("Error compiling network: {e:?}"); + return; + } }; - self.monitor_nodes = proto_network - .nodes - .iter() - .filter(|(_, node)| node.identifier == "graphene_core::memo::MonitorNode".into()) - .map(|(_, node)| node.original_location.path.clone().unwrap_or_default()) - .collect::>(); - assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?"); - self.executor.update(proto_network).await.map_err(|e| { - self.node_graph_errors.clone_from(&e); - format!("{e:?}") - }) + assert_ne!(proto_network.len(), 0, "No proto nodes exist?"); + let result = match self.executor.update(proto_network).await { + Ok((types_to_add, types_to_remove)) => { + // Used to remove thumbnails from the mapping of SNI to rendered SVG strings on the frontend, which occurs when the SNI is removed + // When native frontend rendering is possible, the strings can just be stored in the network interface for each protonode with the rest of the type metadata + Ok(CompilationMetadata { + protonode_callers_for_value, + protonode_callers_for_node, + types_to_add, + types_to_remove, + }) + } + Err(e) => { + self.node_graph_errors.clone_from(&e); + Err(format!("{e:?}")) + } + }; } async fn execute_network(&mut self, render_config: RenderConfig) -> Result { @@ -269,117 +243,6 @@ impl NodeRuntime { Ok(result) } - - /// Updates state data - pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque, update_thumbnails: bool) { - // TODO: Consider optimizing this since it's currently O(m*n^2), with a sort it could be made O(m * n*log(n)) - self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id))); - - for monitor_node_path in &self.monitor_nodes { - // Skip the inspect monitor node - if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) { - continue; - } - // The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID - let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else { - warn!("Monitor node has invalid node id"); - - continue; - }; - - // Extract the monitor node's stored `GraphicElement` data. - let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else { - // TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds) - #[cfg(debug_assertions)] - warn!("Failed to introspect monitor node {}", self.executor.introspect(monitor_node_path).unwrap_err()); - - continue; - }; - - if let Some(io) = introspected_data.downcast_ref::>() { - Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) - } else if let Some(io) = introspected_data.downcast_ref::>() { - Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) - // Insert the vector modify if we are dealing with vector data - } else if let Some(record) = introspected_data.downcast_ref::>() { - let default = Instance::default(); - self.vector_modify.insert( - parent_network_node_id, - record.output.instance_ref_iter().next().unwrap_or_else(|| default.to_instance_ref()).instance.clone(), - ); - } else { - log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}"); - } - } - } - - // If this is `GraphicElement` data: - // Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI. - fn process_graphic_element( - thumbnail_renders: &mut HashMap>, - parent_network_node_id: NodeId, - graphic_element: &impl GraphicElementRendered, - responses: &mut VecDeque, - update_thumbnails: bool, - ) { - // RENDER THUMBNAIL - - if !update_thumbnails { - return; - } - - // Skip thumbnails if the layer is too complex (for performance) - if graphic_element.render_complexity() > 1000 { - let old = thumbnail_renders.insert(parent_network_node_id, Vec::new()); - if old.is_none_or(|v| !v.is_empty()) { - responses.push_back(FrontendMessage::UpdateNodeThumbnail { - id: parent_network_node_id, - value: "Dense thumbnail omitted for performance".to_string(), - }); - } - return; - } - - let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true); - - // Render the thumbnail from a `GraphicElement` into an SVG string - let render_params = RenderParams { - view_mode: ViewMode::Normal, - culling_bounds: bounds, - thumbnail: true, - hide_artboards: false, - for_export: false, - for_mask: false, - alignment_parent_transform: None, - }; - let mut render = SvgRender::new(); - graphic_element.render_svg(&mut render, &render_params); - - // And give the SVG a viewbox and outer ... wrapper tag - let [min, max] = bounds.unwrap_or_default(); - render.format_svg(min, max); - - // UPDATE FRONTEND THUMBNAIL - - let new_thumbnail_svg = render.svg; - let old_thumbnail_svg = thumbnail_renders.entry(parent_network_node_id).or_default(); - - if old_thumbnail_svg != &new_thumbnail_svg { - responses.push_back(FrontendMessage::UpdateNodeThumbnail { - id: parent_network_node_id, - value: new_thumbnail_svg.to_svg_string(), - }); - *old_thumbnail_svg = new_thumbnail_svg; - } - } -} - -pub async fn introspect_node(path: &[NodeId]) -> Result, IntrospectError> { - let runtime = NODE_RUNTIME.lock(); - if let Some(ref mut runtime) = runtime.as_ref() { - return runtime.executor.introspect(path); - } - Err(IntrospectError::RuntimeNotReady) } pub async fn run_node_graph() -> bool { @@ -394,81 +257,3 @@ pub async fn replace_node_runtime(runtime: NodeRuntime) -> Option { let mut node_runtime = NODE_RUNTIME.lock(); node_runtime.replace(runtime) } - -/// Which node is inspected and which monitor node is used (if any) for the current execution -#[derive(Debug, Clone, Copy)] -struct InspectState { - inspect_node: NodeId, - monitor_node: NodeId, -} -/// The resulting value from the temporary inspected during execution -#[derive(Clone, Debug, Default)] -#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] -pub struct InspectResult { - #[cfg(not(feature = "decouple-execution"))] - introspected_data: Option>, - #[cfg(feature = "decouple-execution")] - introspected_data: Option, - pub inspect_node: NodeId, -} - -impl InspectResult { - pub fn take_data(&mut self) -> Option> { - #[cfg(not(feature = "decouple-execution"))] - return self.introspected_data.clone(); - - #[cfg(feature = "decouple-execution")] - return self.introspected_data.take().map(|value| value.to_any()); - } -} - -// This is very ugly but is required to be inside a message -impl PartialEq for InspectResult { - fn eq(&self, other: &Self) -> bool { - self.inspect_node == other.inspect_node - } -} - -impl InspectState { - /// Insert the monitor node to manage the inspection - pub fn monitor_inspect_node(network: &mut NodeNetwork, inspect_node: NodeId) -> Self { - let monitor_id = NodeId::new(); - - // It is necessary to replace the inputs before inserting the monitor node to avoid changing the input of the new monitor node - for input in network.nodes.values_mut().flat_map(|node| node.inputs.iter_mut()).chain(&mut network.exports) { - let NodeInput::Node { node_id, output_index, .. } = input else { continue }; - // We only care about the primary output of our inspect node - if *output_index != 0 || *node_id != inspect_node { - continue; - } - - *node_id = monitor_id; - } - - let monitor_node = DocumentNode { - inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node - implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), - manual_composition: Some(graph_craft::generic!(T)), - skip_deduplication: true, - ..Default::default() - }; - network.nodes.insert(monitor_id, monitor_node); - - Self { - inspect_node, - monitor_node: monitor_id, - } - } - /// Resolve the result from the inspection by accessing the monitor node - fn access(&self, executor: &DynamicExecutor) -> Option { - let introspected_data = executor.introspect(&[self.monitor_node]).inspect_err(|e| warn!("Failed to introspect monitor node {e}")).ok(); - // TODO: Consider displaying the error instead of ignoring it - #[cfg(feature = "decouple-execution")] - let introspected_data = introspected_data.as_ref().and_then(|data| TaggedValue::try_from_std_any_ref(data).ok()); - - Some(InspectResult { - inspect_node: self.inspect_node, - introspected_data, - }) - } -} diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 28b777f806..c7979aa677 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -124,10 +124,10 @@ export class SendUIMetadata extends JsMessage { readonly nodeTypes!: FrontendNodeType[]; } -export class UpdateNodeThumbnail extends JsMessage { - readonly id!: bigint; +export class UpdateThumbnails extends JsMessage { + readonly add!: [bigint, string][]; - readonly value!: string; + readonly clear!: bigint[]; } export class UpdateNodeGraphSelection extends JsMessage { @@ -1683,7 +1683,7 @@ export const messageMakers: Record = { UpdateNodeGraphTransform, UpdateNodeGraphControlBarLayout, UpdateNodeGraphSelection, - UpdateNodeThumbnail, + UpdateThumbnails: UpdateThumbnail, UpdateOpenDocumentsList, UpdatePropertyPanelSectionsLayout, UpdateSpreadsheetLayout, diff --git a/frontend/src/state-providers/node-graph.ts b/frontend/src/state-providers/node-graph.ts index 9884659340..e0c8128bb7 100644 --- a/frontend/src/state-providers/node-graph.ts +++ b/frontend/src/state-providers/node-graph.ts @@ -24,7 +24,7 @@ import { UpdateNodeGraphWires, UpdateNodeGraphSelection, UpdateNodeGraphTransform, - UpdateNodeThumbnail, + UpdateThumbnails, UpdateWirePathInProgress, } from "@graphite/messages"; @@ -168,9 +168,14 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); - editor.subscriptions.subscribeJsMessage(UpdateNodeThumbnail, (updateNodeThumbnail) => { + editor.subscriptions.subscribeJsMessage(UpdateThumbnails, (updateThumbnail) => { update((state) => { - state.thumbnails.set(updateNodeThumbnail.id, updateNodeThumbnail.value); + for (const [id, value] of updateThumbnail.add) { + state.thumbnails.set(id, value); + } + for (const id of updateThumbnail.clear) { + state.thumbnails.set(id, ""); + } return state; }); }); diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index cd2f500f2f..502f91bb8c 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -234,6 +234,7 @@ impl CloneVarArgs for Arc { } } +// Lifetime isnt necessary? pub type Context<'a> = Option>; type DynRef<'a> = &'a (dyn Any + Send + Sync); type DynBox = Box; diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 4f1f0aaf0b..53b6b02a67 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -37,6 +37,7 @@ pub use context::*; pub use ctor; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; +pub use memo::IntrospectMode; pub use memo::MemoHash; pub use num_traits; pub use raster::Color; @@ -58,11 +59,18 @@ pub trait Node<'i, Input> { fn node_name(&self) -> &'static str { std::any::type_name::() } - /// Serialize the node which is used for the `introspect` function which can retrieve values from monitor nodes. - fn serialize(&self) -> Option> { - log::warn!("Node::serialize not implemented for {}", std::any::type_name::()); + + /// Get the call argument or output data for the monitor node on the next evaluation after set_introspect_input + /// Also returns a boolean of whether the node was evaluated + fn introspect(&self, _introspect_mode: IntrospectMode) -> Option> { + log::warn!("Node::introspect not implemented for {}", std::any::type_name::()); None } + + // The introspect mode is set before the graph evaluation, and tells the monitor node what data to store + fn set_introspect(&self, _introspect_mode: IntrospectMode) { + log::warn!("Node::set_introspect not implemented for {}", std::any::type_name::()); + } } mod types; diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index 1a124d2068..73ba5077b9 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -107,47 +107,73 @@ pub mod impure_memo { pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode"); } -/// Stores both what a node was called with and what it returned. -#[derive(Clone, Debug)] -pub struct IORecord { - pub input: I, - pub output: O, +#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub enum IntrospectMode { + Input, + Data, } /// Caches the output of the last graph evaluation for introspection #[derive(Default)] -pub struct MonitorNode { +pub struct MonitorNode { #[allow(clippy::type_complexity)] - io: Arc>>>>, + input: Arc>>>, + output: Arc>>>, + // Gets set to true by the editor when before evaluating the network, then reset when the monitor node is evaluated + introspect_input: Arc>, + introspect_output: Arc>, node: N, } -impl<'i, T, I, N> Node<'i, I> for MonitorNode +impl<'i, I, O, N> Node<'i, I> for MonitorNode where I: Clone + 'static + Send + Sync, - T: Clone + 'static + Send + Sync, - for<'a> N: Node<'a, I, Output: Future + WasmNotSend> + 'i, + O: Clone + 'static + Send + Sync, + for<'a> N: Node<'a, I, Output: Future + WasmNotSend> + Send + Sync + 'i, { - type Output = DynFuture<'i, T>; + type Output = DynFuture<'i, O>; fn eval(&'i self, input: I) -> Self::Output { - let io = self.io.clone(); - let output_fut = self.node.eval(input.clone()); Box::pin(async move { - let output = output_fut.await; - *io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() })); + let output = self.node.eval(input.clone()).await; + let mut introspect_input = self.introspect_input.lock().unwrap(); + if *introspect_input { + *self.input.lock().unwrap() = Some(Box::new(input)); + *introspect_input = false; + } + let mut introspect_output = self.introspect_output.lock().unwrap(); + if *introspect_output { + *self.output.lock().unwrap() = Some(Box::new(output.clone())); + *introspect_output = false; + } output }) } - fn serialize(&self) -> Option> { - let io = self.io.lock().unwrap(); - (io).as_ref().map(|output| output.clone() as Arc) + // After introspecting, the input/output get set to None because the Arc is moved to the editor where it can be directly accessed. + fn introspect(&self, introspect_mode: IntrospectMode) -> Option> { + match introspect_mode { + IntrospectMode::Input => self.input.lock().unwrap().take().map(|input| input as Box), + IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Box), + } + } + + fn set_introspect(&self, introspect_mode: IntrospectMode) { + match introspect_mode { + IntrospectMode::Input => *self.introspect_input.lock().unwrap() = true, + IntrospectMode::Data => *self.introspect_output.lock().unwrap() = true, + } } } -impl MonitorNode { - pub fn new(node: N) -> MonitorNode { - MonitorNode { io: Arc::new(Mutex::new(None)), node } +impl MonitorNode { + pub fn new(node: N) -> MonitorNode { + MonitorNode { + input: Arc::new(Mutex::new(None)), + output: Arc::new(Mutex::new(None)), + introspect_input: Arc::new(Mutex::new(false)), + introspect_output: Arc::new(Mutex::new(false)), + node, + } } } diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 0ef40a86a4..164d0df280 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -24,10 +24,6 @@ where fn reset(&self) { self.0.reset(); } - - fn serialize(&self) -> Option> { - self.0.serialize() - } } impl<'i, N: for<'a> Node<'a, I>, I: 'i> TypeNode>::Output> { pub fn new(node: N) -> Self { diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 5d405df093..43c7fbe6d4 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -132,6 +132,7 @@ pub type TypeErasedPinned<'n> = Pin>>; pub type SharedNodeContainer = std::sync::Arc; pub type NodeConstructor = fn(Vec) -> DynFuture<'static, TypeErasedBox<'static>>; +pub type MonitorConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>; #[derive(Clone)] pub struct NodeContainer { @@ -208,11 +209,10 @@ where #[inline] fn eval(&'input self, input: I) -> Self::Output { { - let node_name = self.node.node_name(); let input = Box::new(input); let future = self.node.eval(input); Box::pin(async move { - let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode Input {e} in: \n{node_name}")); + let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode Input {e} in: \n{:?}", self.node.node_name())); *out }) } @@ -220,11 +220,8 @@ where fn reset(&self) { self.node.reset(); } - - fn serialize(&self) -> Option> { - self.node.serialize() - } } + impl DowncastBothNode { pub const fn new(node: SharedNodeContainer) -> Self { Self { @@ -234,6 +231,11 @@ impl DowncastBothNode { } } } + +pub fn downcast_node(n: SharedNodeContainer) -> DowncastBothNode { + DowncastBothNode::new(n) +} + pub struct FutureWrapperNode { node: Node, } @@ -252,11 +254,6 @@ where fn reset(&self) { self.node.reset(); } - - #[inline(always)] - fn serialize(&self) -> Option> { - self.node.serialize() - } } impl FutureWrapperNode { @@ -294,10 +291,6 @@ where fn reset(&self) { self.node.reset(); } - - fn serialize(&self) -> Option> { - self.node.serialize() - } } impl<'input, I, O, N> DynAnyNode where diff --git a/node-graph/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index b6488c573c..24ef44fa69 100644 --- a/node-graph/gcore/src/structural.rs +++ b/node-graph/gcore/src/structural.rs @@ -1,4 +1,4 @@ -use crate::Node; +use crate::registry::Node; use std::marker::PhantomData; /// This is how we can generically define composition of two nodes. diff --git a/node-graph/gcore/src/uuid.rs b/node-graph/gcore/src/uuid.rs index 3df5007e08..6f252dcf64 100644 --- a/node-graph/gcore/src/uuid.rs +++ b/node-graph/gcore/src/uuid.rs @@ -84,3 +84,12 @@ impl std::fmt::Display for NodeId { write!(f, "{}", self.0) } } + +// Stable Node Id of a protonode, generated during compilation based on the input values +pub type SNI = NodeId; + +// An input of a compiled protonode, used to reference thumbnails, which are stored on a per input basis +pub type CompiledProtonodeInput = (NodeId, usize); + +// Path to the protonode in the document network +pub type ProtonodePath = Box<[NodeId]>; diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 08679ce417..88b36a6ed4 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -1,28 +1,18 @@ pub mod value; use crate::document::value::TaggedValue; -use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; +use crate::proto::{ConstructionArgs, NodeConstructionArgs, OriginalLocation, ProtoNode}; use dyn_any::DynAny; use glam::IVec2; use graphene_core::memo::MemoHashGuard; -pub use graphene_core::uuid::NodeId; pub use graphene_core::uuid::generate_uuid; -use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type}; -use log::Metadata; +use graphene_core::uuid::{CompiledProtonodeInput, NodeId, ProtonodePath, SNI}; +use graphene_core::{Context, Cow, MemoHash, ProtoNodeIdentifier, Type}; use rustc_hash::FxHashMap; -use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; +use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; -/// Hash two IDs together, returning a new ID that is always consistent for two input IDs in a specific order. -/// This is used during [`NodeNetwork::flatten`] in order to ensure consistent yet non-conflicting IDs for inner networks. -fn merge_ids(a: NodeId, b: NodeId) -> NodeId { - let mut hasher = DefaultHasher::new(); - a.hash(&mut hasher); - b.hash(&mut hasher); - NodeId(hasher.finish()) -} - /// Utility function for providing a default boolean value to serde. #[inline(always)] fn return_true() -> bool { @@ -32,7 +22,7 @@ fn return_true() -> bool { /// An instance of a [`DocumentNodeDefinition`] that has been instantiated in a [`NodeNetwork`]. /// Currently, when an instance is made, it lives all on its own without any lasting connection to the definition. /// But we will want to change it in the future so it merely references its definition. -#[derive(Clone, Debug, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] pub struct DocumentNode { /// The inputs to a node, which are either: /// - From other nodes within this graph [`NodeInput::Node`], @@ -44,238 +34,32 @@ pub struct DocumentNode { /// by using network.update_click_target(node_id). #[cfg_attr(target_arch = "wasm32", serde(alias = "outputs"))] pub inputs: Vec, - /// Manual composition is the methodology by which most nodes are implemented, involving a call argument and upstream inputs. - /// By contrast, automatic composition is an alternative way to handle the composition of nodes as they execute in the graph. - /// Normally, the program (the compiled graph) builds up its call stack, with each node calling its upstream predecessor to acquire its input data. - /// When the document graph becomes the proto graph, that conceptual model changes into a model that's unique to the proto graph. - /// Automatic composition allows a document node to be translated into its place in the proto graph differently, such that - /// the node doesn't participate in that process of being called with a call argument and calling its upstream predecessor. - /// Instead, it is called directly with its input data from the upstream node, skipping the call stack building process. - /// The abstraction is provided by the compiler for nodes which opt for automatic composition. It works by inserting a `ComposeNode` - /// into the proto graph, which does the job of calling the upstream node and feeding its output into the downstream node's first input. - /// That first input is typically used by manual composition nodes as the call argument, but for automatic composition nodes, - /// that first input becomes the input data from the upstream node passed in by the `ComposeNode`. - /// - /// Through automatic composition, the upstream node providing the first input for a proto node is evaluated before the proto node itself is run. - /// (That first input is usually the call argument when manual composition is used.) - /// - Abstract example: upstream node `G` is evaluated and its data feeds into the first input of downstream node `F`, - /// just like function composition where function `G` is evaluated and its result is fed into function `F`. - /// - Concrete example: a node that takes an image as its first input will get that image data from an upstream node that produces image output data and is evaluated first before being fed downstream. - /// - /// This is achieved by automatically inserting `ComposeNode`s, which run the first node with the overall input and then feed the resulting output into the second node. - /// The `ComposeNode` is basically a function composition operator: the parentheses in `F(G(x))` or circle math operator in `(F ∘ G)(x)`. - /// For flexibility, instead of being a language construct, Graphene splits out composition itself as its own low-level node so that behavior can be overridden. - /// The `ComposeNode`s are then inserted during the graph rewriting step for nodes that don't opt out with `manual_composition`. - /// Instead of node `G` feeding into node `F` feeding as the result back to the caller, - /// the graph is rewritten so nodes `G` and `F` both feed as lambdas into the inputs of a `ComposeNode` which calls `F(G(input))` and returns the result to the caller. - /// - /// A node's manual composition input represents an input that is not resolved through graph rewriting with a `ComposeNode`, - /// and is instead just passed in when evaluating this node within the borrow tree. - /// This is similar to having the first input be a `NodeInput::Network` after the graph flattening. - /// - /// ## Example Use Case: CacheNode - /// - /// The `CacheNode` is a pass-through node on cache miss, but on cache hit it needs to avoid evaluating the upstream node and instead just return the cached value. - /// - /// First, let's consider what that would look like using the default composition flow if the `CacheNode` instead just always acted as a pass-through (akin to a cache that always misses): - /// - /// ```text - /// ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ - /// │ │◄───┤ │◄───┤ │◄─── EVAL (START) - /// │ G │ │PassThroughNode│ │ F │ - /// │ ├───►│ ├───►│ │───► RESULT (END) - /// └───────────────┘ └───────────────┘ └───────────────┘ - /// ``` - /// - /// This acts like the function call `F(PassThroughNode(G(input)))` when evaluating `F` with some `input`: `F.eval(input)`. - /// - The diagram's upper track of arrows represents the flow of building up the call stack: - /// since `F` is the output it is encountered first but deferred to its upstream caller `PassThroughNode` and that is once again deferred to its upstream caller `G`. - /// - The diagram's lower track of arrows represents the flow of evaluating the call stack: - /// `G` is evaluated first, then `PassThroughNode` is evaluated with the result of `G`, and finally `F` is evaluated with the result of `PassThroughNode`. - /// - /// With the default composition flow (no manual composition), `ComposeNode`s would be automatically inserted during the graph rewriting step like this: - /// - /// ```text - /// ┌───────────────┐ - /// │ │◄─── EVAL (START) - /// │ ComposeNode │ - /// ┌───────────────┐ │ ├───► RESULT (END) - /// │ │◄─┐ ├───────────────┤ - /// │ G │ └─┤ │ - /// │ ├─┐ │ First │ - /// └───────────────┘ └─►│ │ - /// ┌───────────────┐ ├───────────────┤ - /// │ │◄───┤ │ - /// │ ComposeNode │ │ Second │ - /// ┌───────────────┐ │ ├───►│ │ - /// │ │◄─┐ ├───────────────┤ └───────────────┘ - /// │PassThroughNode│ └─┤ │ - /// │ ├─┐ │ First │ - /// └───────────────┘ └─►│ │ - /// ┌───────────────┐ ├───────────────┤ - /// | │◄───┤ │ - /// │ F │ │ Second │ - /// │ ├───►│ │ - /// └───────────────┘ └───────────────┘ - /// ``` - /// - /// Now let's swap back from the `PassThroughNode` to the `CacheNode` to make caching actually work. - /// It needs to override the default composition flow so that `G` is not automatically evaluated when the cache is hit. - /// We need to give the `CacheNode` more manual control over the order of execution. - /// So the `CacheNode` opts into manual composition and, instead of deferring to its upstream caller, it consumes the input directly: - /// - /// ```text - /// ┌───────────────┐ ┌───────────────┐ - /// │ │◄───┤ │◄─── EVAL (START) - /// │ CacheNode │ │ F │ - /// │ ├───►│ │───► RESULT (END) - /// ┌───────────────┐ ├───────────────┤ └───────────────┘ - /// │ │◄───┤ │ - /// │ G │ │ Cached Data │ - /// │ ├───►│ │ - /// └───────────────┘ └───────────────┘ - /// ``` - /// - /// Now, the call from `F` directly reaches the `CacheNode` and the `CacheNode` can decide whether to call `G.eval(input_from_f)` - /// in the event of a cache miss or just return the cached data in the event of a cache hit. - pub manual_composition: Option, // A nested document network or a proto-node identifier. pub implementation: DocumentNodeImplementation, /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step. #[serde(default = "return_true")] pub visible: bool, - /// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed. - /// See [`ProtoNetwork::generate_stable_node_ids`] for details. - /// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph. + pub manual_composition: Option, #[serde(default)] pub skip_deduplication: bool, - /// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called. - #[serde(skip)] - pub original_location: OriginalLocation, } -/// Represents the original location of a node input/output when [`NodeNetwork::generate_node_paths`] was called, allowing the types and errors to be derived. -#[derive(Clone, Debug, PartialEq, Eq, Hash, DynAny, serde::Serialize, serde::Deserialize)] -pub struct Source { - pub node: Vec, - pub index: usize, -} - -/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called. -#[derive(Clone, Debug, PartialEq, Eq, DynAny, Default, serde::Serialize, serde::Deserialize)] -#[non_exhaustive] -pub struct OriginalLocation { - /// The original location to the document node - e.g. [grandparent_id, parent_id, node_id]. - pub path: Option>, - /// Each document input source maps to one proto node input (however one proto node input may come from several sources) - pub inputs_source: HashMap, - /// List of nodes which depend on this node - pub dependants: Vec>, - /// A list of flags indicating whether the input is exposed in the UI - pub inputs_exposed: Vec, - /// Skipping inputs is useful for the manual composition thing - whereby a hidden `Footprint` input is added as the first input. - pub skip_inputs: usize, +impl Hash for DocumentNode { + fn hash(&self, state: &mut H) { + self.inputs.hash(state); + self.implementation.hash(state); + self.visible.hash(state); + } } impl Default for DocumentNode { fn default() -> Self { Self { inputs: Default::default(), - manual_composition: Default::default(), implementation: Default::default(), visible: true, - skip_deduplication: Default::default(), - original_location: OriginalLocation::default(), - } - } -} - -impl Hash for OriginalLocation { - fn hash(&self, state: &mut H) { - self.path.hash(state); - self.inputs_source.iter().for_each(|val| val.hash(state)); - self.inputs_exposed.hash(state); - self.skip_inputs.hash(state); - } -} -impl OriginalLocation { - pub fn inputs(&self, index: usize) -> impl Iterator + '_ { - [(index >= self.skip_inputs).then(|| Source { - node: self.path.clone().unwrap_or_default(), - index: self.inputs_exposed.iter().take(index - self.skip_inputs).filter(|&&exposed| exposed).count(), - })] - .into_iter() - .flatten() - .chain(self.inputs_source.iter().filter(move |x| *x.1 == index).map(|(source, _)| source.clone())) - } -} -impl DocumentNode { - /// Locate the input that is a [`NodeInput::Network`] at index `offset` and replace it with a [`NodeInput::Node`]. - pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, lambda: bool, source: impl Iterator, skip: usize) { - let (index, _) = self - .inputs - .iter() - .enumerate() - .nth(offset) - .unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}")); - - self.inputs[index] = NodeInput::Node { node_id, output_index, lambda }; - let input_source = &mut self.original_location.inputs_source; - for source in source { - input_source.insert(source, (index + self.original_location.skip_inputs).saturating_sub(skip)); - } - } - - fn resolve_proto_node(mut self) -> ProtoNode { - assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {self:#?} with no inputs"); - let DocumentNodeImplementation::ProtoNode(identifier) = self.implementation else { - unreachable!("tried to resolve not flattened node on resolved node {self:?}"); - }; - - let (input, mut args) = if let Some(ty) = self.manual_composition { - (ProtoNodeInput::ManualComposition(ty), ConstructionArgs::Nodes(vec![])) - } else { - let first = self.inputs.remove(0); - match first { - NodeInput::Value { tagged_value, .. } => { - assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs); - (ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context<'static>)), ConstructionArgs::Value(tagged_value)) - } - NodeInput::Node { node_id, output_index, lambda } => { - assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node"); - let node = if lambda { ProtoNodeInput::NodeLambda(node_id) } else { ProtoNodeInput::Node(node_id) }; - (node, ConstructionArgs::Nodes(vec![])) - } - NodeInput::Network { import_type, .. } => (ProtoNodeInput::ManualComposition(import_type), ConstructionArgs::Nodes(vec![])), - NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)), - NodeInput::Scope(_) => unreachable!("Scope input was not resolved"), - NodeInput::Reflection(_) => unreachable!("Reflection input was not resolved"), - } - }; - assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network { .. })), "received non-resolved input"); - assert!( - !self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })), - "received value as input. inputs: {:#?}, construction_args: {:#?}", - self.inputs, - args - ); - - // If we have one input of the type inline, set it as the construction args - if let &[NodeInput::Inline(ref inline)] = self.inputs.as_slice() { - args = ConstructionArgs::Inline(inline.clone()); - } - if let ConstructionArgs::Nodes(nodes) = &mut args { - nodes.extend(self.inputs.iter().map(|input| match input { - NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda), - _ => unreachable!(), - })); - } - ProtoNode { - identifier, - input, - construction_args: args, - original_location: self.original_location, - skip_deduplication: self.skip_deduplication, + manual_composition: Some(generic!(T)), + skip_deduplication: false, } } } @@ -289,9 +73,8 @@ pub enum NodeInput { /// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation. Value { tagged_value: MemoHash, exposed: bool }, - // TODO: Remove import_type and get type from parent node input /// Input that is provided by the parent network to this document node, instead of from a hardcoded value or another node within the same network. - Network { import_type: Type, import_index: usize }, + Network { import_index: usize, import_type: Type }, /// Input that is extracted from the parent scopes the node resides in. The string argument is the key. Scope(Cow<'static, str>), @@ -343,16 +126,6 @@ impl NodeInput { Self::Scope(key.into()) } - fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) { - 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 { match self { NodeInput::Node { .. } => true, @@ -368,10 +141,10 @@ impl NodeInput { match self { NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"), NodeInput::Value { tagged_value, .. } => tagged_value.ty(), - NodeInput::Network { import_type, .. } => import_type.clone(), + NodeInput::Network { .. } => unreachable!("ty() called on NodeInput::Network"), NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"), NodeInput::Scope(_) => unreachable!("ty() called on NodeInput::Scope"), - NodeInput::Reflection(_) => concrete!(Metadata), + NodeInput::Reflection(_) => concrete!(DocumentNodeMetadata), } } @@ -388,6 +161,13 @@ impl NodeInput { pub fn as_node(&self) -> Option { if let NodeInput::Node { node_id, .. } = self { Some(*node_id) } else { None } } + + pub fn is_lambda(&self) -> bool { + match self { + NodeInput::Node { lambda, .. } => *lambda, + _ => false, + } + } } #[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] @@ -537,50 +317,25 @@ where /// But we will want to change it in the future so it merely references its definition. #[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] pub struct OldDocumentNode { - /// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the node definition's name is displayed to the user in italics. - /// Ensure the click target in the encapsulating network is updated when this is modified by using network.update_click_target(node_id). #[serde(default)] pub alias: String, - // TODO: Replace this name with a reference to the [`DocumentNodeDefinition`] node definition to use the name from there instead. - /// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties. #[serde(deserialize_with = "migrate_layer_to_merge")] pub name: String, - /// The inputs to a node, which are either: - /// - From other nodes within this graph [`NodeInput::Node`], - /// - A constant value [`NodeInput::Value`], - /// - A [`NodeInput::Network`] which specifies that this input is from outside the graph, which is resolved in the graph flattening step in the case of nested networks. - /// - /// In the root network, it is resolved when evaluating the borrow tree. - /// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id). #[cfg_attr(target_arch = "wasm32", serde(alias = "outputs"))] pub inputs: Vec, pub manual_composition: Option, - // TODO: Remove once this references its definition instead (see above TODO). - /// Indicates to the UI if a primary output should be drawn for this node. - /// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output. #[serde(default = "return_true")] pub has_primary_output: bool, - // A nested document network or a proto-node identifier. pub implementation: OldDocumentNodeImplementation, - /// User chosen state for displaying this as a left-to-right node or bottom-to-top layer. Ensure the click target in the encapsulating network is updated when the node changes to a layer by using network.update_click_target(node_id). #[serde(default)] pub is_layer: bool, - /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step. #[serde(default = "return_true")] pub visible: bool, - /// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI. #[serde(default)] pub locked: bool, - /// Metadata about the node including its position in the graph UI. Ensure the click target in the encapsulating network is updated when the node moves by using network.update_click_target(node_id). pub metadata: OldDocumentNodeMetadata, - /// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed. - /// See [`ProtoNetwork::generate_stable_node_ids`] for details. - /// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph. #[serde(default)] pub skip_deduplication: bool, - /// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called. - #[serde(skip)] - pub original_location: OriginalLocation, } // TODO: Eventually remove this document upgrade code @@ -667,7 +422,7 @@ pub struct NodeNetwork { /// A network may expose nodes as constants which can by used by other nodes using a `NodeInput::Scope(key)`. #[serde(default)] #[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")] - pub scope_injections: FxHashMap, + pub scope_injections: FxHashMap, #[serde(skip)] pub generated: bool, } @@ -690,7 +445,7 @@ impl PartialEq for NodeNetwork { } } -/// Graph modification functions +/// Graph helper functions impl NodeNetwork { pub fn current_hash(&self) -> u64 { let mut hasher = DefaultHasher::new(); @@ -698,14 +453,6 @@ impl NodeNetwork { hasher.finish() } - pub fn value_network(node: DocumentNode) -> Self { - Self { - exports: vec![NodeInput::node(NodeId(0), 0)], - nodes: [(NodeId(0), node)].into_iter().collect(), - ..Default::default() - } - } - /// Get the nested network given by the path of node ids pub fn nested_network(&self, nested_path: &[NodeId]) -> Option<&Self> { let mut network = Some(self); @@ -716,6 +463,14 @@ impl NodeNetwork { network } + pub fn value_network(node: DocumentNode) -> Self { + Self { + exports: vec![NodeInput::node(NodeId(0), 0)], + nodes: [(NodeId(0), node)].into_iter().collect(), + ..Default::default() + } + } + /// Get the mutable nested network given by the path of node ids pub fn nested_network_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> { let mut network = Some(self); @@ -726,13 +481,6 @@ impl NodeNetwork { network } - /// Is the node being used directly as an output? - pub fn outputs_contain(&self, node_id_to_check: NodeId) -> bool { - self.exports - .iter() - .any(|output| if let NodeInput::Node { node_id, .. } = output { *node_id == node_id_to_check } else { false }) - } - /// Check there are no cycles in the graph (this should never happen). pub fn is_acyclic(&self) -> bool { let mut dependencies: HashMap> = HashMap::new(); @@ -761,887 +509,909 @@ impl NodeNetwork { /// Functions for compiling the network impl NodeNetwork { - /// Replace all references in the graph of a node ID with a new node ID defined by the function `f`. - pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) { - self.exports.iter_mut().for_each(|output| { - if let NodeInput::Node { node_id, .. } = output { - *node_id = f(*node_id) - } - }); - self.scope_injections.values_mut().for_each(|(id, _ty)| *id = f(*id)); - let nodes = std::mem::take(&mut self.nodes); - self.nodes = nodes - .into_iter() - .map(|(id, mut node)| { - node.inputs.iter_mut().for_each(|input| input.map_ids(f)); - node.original_location.dependants.iter_mut().for_each(|deps| deps.iter_mut().for_each(|id| *id = f(*id))); - (f(id), node) - }) - .collect(); - } + // Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation + // Compiles a network with one export where any scope injections are added the top level network, and the network to run is implemented as a DocumentNodeImplementation::Network + // The traversal input is the node which calls the network to be flattened. If it is None, then start from the export. + // Every value protonode stores the connector which directly called it, which is used to map the value input to the protonode caller. + // Every value input connector is mapped to its caller, and every protonode is mapped to its caller. If there are multiple, then they are compared to ensure it is the same between compilations + pub fn flatten(&mut self) -> Result<(Vec, Vec<(AbsoluteInputConnector, CompiledProtonodeInput)>, Vec<(ProtonodePath, CompiledProtonodeInput)>), String> { + // These three arrays are stored in parallel + let mut protonetwork = Vec::new(); + let mut value_connectors = Vec::new(); + let mut protonode_paths = Vec::new(); + let mut calling_protonodes = HashMap::new(); - /// Populate the [`DocumentNode::path`], which stores the location of the document node to allow for matching the resulting proto nodes to the document node for the purposes of typing and finding monitor nodes. - pub fn generate_node_paths(&mut self, prefix: &[NodeId]) { - for (node_id, node) in &mut self.nodes { - let mut new_path = prefix.to_vec(); - if !self.generated { - new_path.push(*node_id); - } - if let DocumentNodeImplementation::Network(network) = &mut node.implementation { - network.generate_node_paths(new_path.as_slice()); - } - if node.original_location.path.is_some() { - log::warn!("Attempting to overwrite node path"); - } else { - node.original_location = OriginalLocation { - path: Some(new_path), - inputs_exposed: node.inputs.iter().map(|input| input.is_exposed()).collect(), - skip_inputs: if node.manual_composition.is_some() { 1 } else { 0 }, - dependants: (0..node.implementation.output_count()).map(|_| Vec::new()).collect(), - ..Default::default() - }; - } - } - } - - pub fn populate_dependants(&mut self) { - let mut dep_changes = Vec::new(); - for (node_id, node) in &mut self.nodes { - let len = node.original_location.dependants.len(); - node.original_location.dependants.extend(vec![vec![]; (node.implementation.output_count()).max(len) - len]); - for input in &node.inputs { - if let NodeInput::Node { node_id: dep_id, output_index, .. } = input { - dep_changes.push((*dep_id, *output_index, *node_id)); - } - } - } - // println!("{:#?}", self.nodes.get(&NodeId(1))); - for (dep_id, output_index, node_id) in dep_changes { - let node = self.nodes.get_mut(&dep_id).expect("Encountered invalid node id"); - let len = node.original_location.dependants.len(); - // One must be added to the index to find the length because indexing in rust starts from 0. - node.original_location.dependants.extend(vec![vec![]; (output_index + 1).max(len) - len]); - // println!("{node_id} {output_index} {}", node.implementation.output_count()); - node.original_location.dependants[output_index].push(node_id); - } - } - - /// Replace all references in any node of `old_input` with `new_input` - fn replace_node_inputs(&mut self, node_id: NodeId, old_input: (NodeId, usize), new_input: (NodeId, usize)) { - let Some(node) = self.nodes.get_mut(&node_id) else { return }; - node.inputs.iter_mut().for_each(|input| { - if let NodeInput::Node { node_id: input_id, output_index, .. } = input { - if (*input_id, *output_index) == old_input { - (*input_id, *output_index) = new_input; - } - } - }); - } - - /// Replace all references in any node of `old_output` with `new_output` - fn replace_network_outputs(&mut self, old_output: NodeInput, new_output: NodeInput) { - for output in self.exports.iter_mut() { - if *output == old_output { - *output = new_output.clone(); - } - } - } - - /// Removes unused nodes from the graph. Returns a list of booleans which represent if each of the inputs have been retained. - pub fn remove_dead_nodes(&mut self, number_of_inputs: usize) -> Vec { - // Take all the nodes out of the nodes list - let mut old_nodes = std::mem::take(&mut self.nodes); - - let mut stack = self - .exports - .iter() - .filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(*node_id) } else { None }) - .collect::>(); - while let Some(node_id) = stack.pop() { - let Some((node_id, mut document_node)) = old_nodes.remove_entry(&node_id) else { - continue; - }; - // Remove dead nodes from child networks - if let DocumentNodeImplementation::Network(network) = &mut document_node.implementation { - // Remove inputs to the parent node if they have been removed from the child - let mut retain_inputs = network.remove_dead_nodes(document_node.inputs.len()).into_iter(); - document_node.inputs.retain(|_| retain_inputs.next().unwrap_or(true)) - } - // Visit all nodes that this node references - stack.extend( - document_node - .inputs - .iter() - .filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id) } else { None }), - ); - // Add the node back to the list of nodes - self.nodes.insert(node_id, document_node); - } - - // Check if inputs are used and store for return value - let mut are_inputs_used = vec![false; number_of_inputs]; - for node in &self.nodes { - for node_input in &node.1.inputs { - if let NodeInput::Network { import_index, .. } = node_input { - if let Some(is_used) = are_inputs_used.get_mut(*import_index) { - *is_used = true; - } - } - } - } - are_inputs_used - } - - pub fn resolve_scope_inputs(&mut self) { - for node in self.nodes.values_mut() { - for input in node.inputs.iter_mut() { - if let NodeInput::Scope(key) = input { - let (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect("Tried to import a non existent key from scope"); - // TODO use correct output index - *input = NodeInput::node(*import_id, 0); - } - } - } - } - - /// Remove all nodes that contain [`DocumentNodeImplementation::Network`] by moving the nested nodes into the parent network. - pub fn flatten(&mut self, node_id: NodeId) { - self.flatten_with_fns(node_id, merge_ids, NodeId::new) - } - - /// Remove all nodes that contain [`DocumentNodeImplementation::Network`] by moving the nested nodes into the parent network. - pub fn flatten_with_fns(&mut self, node_id: NodeId, map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy, gen_id: impl Fn() -> NodeId + Copy) { - let Some((id, mut node)) = self.nodes.remove_entry(&node_id) else { - warn!("The node which was supposed to be flattened does not exist in the network, id {node_id} network {self:#?}"); - return; - }; - // If the node is hidden, replace it with an identity node - let identity_node = DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()); - if !node.visible && node.implementation != identity_node { - node.implementation = identity_node; - - // Connect layer node to the graphic group below - node.inputs.drain(1..); - node.manual_composition = None; - self.nodes.insert(id, node); - return; - } - - let path = node.original_location.path.clone().unwrap_or_default(); - - // Replace value inputs with dedicated value nodes - if node.implementation != DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()) { - Self::replace_value_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id); - } - - let DocumentNodeImplementation::Network(mut inner_network) = node.implementation else { - // If the node is not a network, it is a primitive node and can be inserted into the network as is. - assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict"); - - self.nodes.insert(id, node); - return; - }; - - // Replace value and reflection imports with value nodes, added inside nested network - Self::replace_value_inputs_with_nodes( - &mut inner_network.exports, - &mut inner_network.nodes, - node.original_location.path.as_ref().unwrap_or(&vec![]), - gen_id, - map_ids, - id, + // This function creates a flattened network with populated original location fields but unmapped inputs + // The input to flattened protonode hashmap is used to map the inputs + self.traverse_input( + &mut protonetwork, + &mut value_connectors, + &mut protonode_paths, + &mut calling_protonodes, + &mut HashMap::new(), + AbsoluteInputConnector::traversal_start(), + (0, 0), ); - // Connect all network inputs to either the parent network nodes, or newly created value nodes for the parent node. - inner_network.map_ids(|inner_id| map_ids(id, inner_id)); - inner_network.populate_dependants(); - let new_nodes = inner_network.nodes.keys().cloned().collect::>(); - - for (key, value) in inner_network.scope_injections.into_iter() { - match self.scope_injections.entry(key) { - std::collections::hash_map::Entry::Occupied(o) => { - log::warn!("Found duplicate scope injection for key {}, ignoring", o.key()); + let mut generated_snis = HashSet::new(); + // Generate SNI's. This gets called after all node inputs are replaced with their indices + for protonode_index in (0..protonetwork.len()).rev() { + let protonode = protonetwork.get_mut(protonode_index).unwrap(); + if let ConstructionArgs::Nodes(NodeConstructionArgs { inputs: input_snis, .. }) = &protonode.construction_args { + for input_sni in input_snis { + assert_ne!( + *input_sni, + NodeId(0), + "All inputs should be mapped to a stable node index, and the calling nodes inputs should be updated" + ); } - std::collections::hash_map::Entry::Vacant(v) => { - v.insert(value); + } + + use std::hash::Hasher; + let mut hasher = rustc_hash::FxHasher::default(); + protonode.construction_args.hash(&mut hasher); + let mut stable_node_id = NodeId(hasher.finish()); + // The stable node index must be unique for every protonode. If it has the same hash as another protonode, continue hashing itself + // For example two cache nodes connected to a Context getter node have two cache different values, even though the stable node id is the same. + while !generated_snis.insert(stable_node_id) { + stable_node_id.hash(&mut hasher); + stable_node_id = NodeId(hasher.finish()); + } + + protonode.stable_node_id = stable_node_id; + for (calling_node_index, input_index) in calling_protonodes.get(&protonode_index).unwrap() { + match &mut protonetwork.get_mut(*calling_node_index).unwrap().construction_args { + ConstructionArgs::Nodes(nodes) => { + *nodes.inputs.get_mut(*input_index).unwrap() = stable_node_id; + } + // TODO: Implement for extract + _ => unreachable!(), } } } - // Match the document node input and the inputs of the inner network - for (nested_node_id, mut nested_node) in inner_network.nodes.into_iter() { - for (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() { - if let NodeInput::Network { import_index, .. } = nested_input { - let parent_input = node.inputs.get(*import_index).unwrap_or_else(|| panic!("Import index {} should always exist", import_index)); - match *parent_input { - // If the input to self is a node, connect the corresponding output of the inner network to it - NodeInput::Node { node_id, output_index, lambda } => { - let skip = node.original_location.skip_inputs; - nested_node.populate_first_network_input(node_id, output_index, nested_input_index, lambda, node.original_location.inputs(*import_index), skip); - let input_node = self.nodes.get_mut(&node_id).unwrap_or_else(|| panic!("unable find input node {node_id:?}")); - input_node.original_location.dependants[output_index].push(nested_node_id); - } - NodeInput::Network { import_index, .. } => { - let parent_input_index = import_index; - let Some(NodeInput::Network { import_index, .. }) = nested_node.inputs.get_mut(nested_input_index) else { - log::error!("Nested node should have a network input"); - continue; - }; - *import_index = parent_input_index; - } - NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"), - NodeInput::Inline(_) => (), - NodeInput::Scope(ref key) => { - let (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect("Tried to import a non existent key from scope"); - // TODO use correct output index - nested_node.inputs[nested_input_index] = NodeInput::node(*import_id, 0); - } - NodeInput::Reflection(_) => unreachable!("Reflection inputs should have been replaced with value nodes"), - } - } - } - self.nodes.insert(nested_node_id, nested_node); - } - // TODO: Add support for flattening exports that are NodeInput::Network (https://github.com/GraphiteEditor/Graphite/issues/1762) + // Do another traversal now that the caller SNI have been generated to collect metadata for the editor + let mut value_connector_callers = Vec::new(); + let mut protonode_callers = Vec::new(); - // Connect all nodes that were previously connected to this node to the nodes of the inner network - for (i, export) in inner_network.exports.into_iter().enumerate() { - if let NodeInput::Node { node_id, output_index, .. } = &export { - for deps in &node.original_location.dependants { - for dep in deps { - self.replace_node_inputs(*dep, (id, i), (*node_id, *output_index)); - } - } + for (protonode_index, (value_connector, protonode_path)) in value_connectors.iter_mut().zip(protonode_paths.iter_mut()).enumerate().rev() { + let callers = calling_protonodes.get(&protonode_index).unwrap(); - if let Some(new_output_node) = self.nodes.get_mut(node_id) { - for dep in &node.original_location.dependants[i] { - new_output_node.original_location.dependants[*output_index].push(*dep); - } - } + let &(min_protonode_index, input_index) = callers.iter().min().unwrap(); + + let protonode_id = protonetwork[min_protonode_index].stable_node_id; + + if let Some(value_connector) = value_connector.take() { + value_connector_callers.push((value_connector, (protonode_id, input_index))); } - self.replace_network_outputs(NodeInput::node(id, i), export); + if let Some(protonode_path) = protonode_path.take() { + protonode_callers.push((protonode_path, (protonode_id, input_index))); + } } - for node_id in new_nodes { - self.flatten_with_fns(node_id, map_ids, gen_id); - } + let mut existing_ids = HashSet::new(); + // Value nodes can be deduplicated if they share the same hash, since they do not depend on the input + let protonetwork = protonetwork + .into_iter() + .filter(|protonode| !(matches!(protonode.construction_args, ConstructionArgs::Value(_)) && !existing_ids.insert(protonode.stable_node_id))) + .collect(); + Ok((protonetwork, value_connector_callers, protonode_callers)) } - #[inline(never)] - fn replace_value_inputs_with_nodes( - inputs: &mut [NodeInput], - collection: &mut FxHashMap, - path: &[NodeId], - gen_id: impl Fn() -> NodeId + Copy, - map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy, - id: NodeId, + fn get_input_from_absolute_connector(&mut self, traversal_input: &AbsoluteInputConnector) -> Option<&mut NodeInput> { + let network_path = &traversal_input.network_path; + let Some(nested_network) = self.nested_network_mut(network_path) else { + log::error!("traversal_input network does not exist, path {:?}", network_path); + return None; + }; + match &traversal_input.connector { + // Input from an export + InputConnector::Export(export_index) => { + let Some(input) = nested_network.exports.get_mut(*export_index) else { + log::error!( + "The output which was supposed to be flattened does not exist in the network {:?}, index {:?}", + &network_path, + export_index + ); + return None; + }; + Some(input) + } + // Input from a protonode or network node + InputConnector::Node { node_id, input_index } => { + let Some(document_node) = nested_network.nodes.get_mut(node_id) else { + log::error!("The node which was supposed to be flattened does not exist in the network, id {node_id}"); + return None; + }; + let Some(input) = document_node.inputs.get_mut(*input_index) else { + log::error!("The output which was supposed to be flattened does not exist in the network, index {input_index}"); + return None; + }; + Some(input) + } + } + } + // Performs a recursive graph traversal starting from all protonode inputs and the root export until reaching the next protonode or value input. + // Automatically inserts value nodes by moving the value from the current network + // + // protonetwork - The topologically sorted flattened protonetwork. The caller of each protonode is at a lower index. The output of the network is the first protonode + // + // calling protonodes - anytime a protonode is reached, the caller is added as a value with (caller protonetwork index, caller input index). + // This is necessary so the calling protonodes input can be looked up and mapped when generating SNI's + // + // Protonode indices - mapping of protonode path to its index in the protonetwork, updated when inserting a protonode + // + // Traversal input - current connector to traverse over. added to downstream_calling_inputs every time the function is called. + // + // downstream_calling_inputs - tracks all inputs reached during traversal + // + // any_input_to_downstream_protonode_input - used by the runtime/javascript to get the calling protonode input from any input connector. + // When a protonode is reached, each input connector in downstream_calling_inputs, is looked up in `any_input_to_downstream_protonode_input`. If there is an entry, + // Then the paths are compared, and the greater one is chosen using stable ordering. + // This is to ensure a constant mapping, since an export for instance can have multiple calling nodes in the parent network + // + // any_input_to_upstream_protonode - used by the runtime to get the node to evaluate for any given input connector. + // Each input connector is inserted into any_input_to_upstream_protonode with the value being the path to the reached protonode. + // It doesnt matter if its overwritten since it must have previously pointed to the same protonode anyways + // + pub fn traverse_input( + &mut self, + protonetwork: &mut Vec, // Flattened node id to protonode, stable node ids can only be generated once the network is fully flattened, since it runs in reverse + value_connector: &mut Vec>, + protonode_path: &mut Vec>, + calling_protonodes: &mut HashMap>, // A mapping of protonode path to all (flattened network indices, their input index) that called the protonode, used during SNI generation to remap inputs + protonode_indices: &mut HashMap, usize>, // Mapping of protonode path to its index in the flattened protonetwork + traversal_input: AbsoluteInputConnector, + // Protonode index, input index + traversal_start: (usize, usize), ) { - // Replace value exports and imports with value nodes, added inside the nested network - for export in inputs { - let export: &mut NodeInput = export; - let previous_export = std::mem::replace(export, NodeInput::network(concrete!(()), 0)); + let network_path = &traversal_input.network_path; - let (tagged_value, exposed) = match previous_export { - NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed), - NodeInput::Reflection(reflect) => match reflect { - DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodePath(path.to_vec()).into(), false), - }, - previous_export => { - *export = previous_export; - continue; + let Some(input) = self.get_input_from_absolute_connector(&traversal_input) else { + return; + }; + + // Populate reflection inputs with the tagged value of the node path + if let NodeInput::Reflection(metadata) = input { + match metadata { + DocumentNodeMetadata::DocumentNodePath => { + let mut node_path = network_path.clone(); + if let Some(traversal_node_id) = traversal_input.connector.node_id() { + node_path.push(traversal_node_id); + } + *input = NodeInput::Value { + tagged_value: TaggedValue::NodePath(node_path).into(), + exposed: true, + } } - }; - let value_node_id = gen_id(); - let merged_node_id = map_ids(id, value_node_id); - let mut original_location = OriginalLocation { - path: Some(path.to_vec()), - dependants: vec![vec![id]], - ..Default::default() - }; - - if let Some(path) = &mut original_location.path { - path.push(value_node_id); } - collection.insert( - merged_node_id, - DocumentNode { - inputs: vec![NodeInput::Value { tagged_value, exposed }], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()), - original_location, - ..Default::default() - }, - ); - *export = NodeInput::Node { - node_id: merged_node_id, - output_index: 0, - lambda: false, + } + + if let NodeInput::Scope(cow) = input { + let string = cow.to_string(); + let scope_node_value = match self.scope_injections.get(&string) { + Some(value) => value.clone(), // Scope injections need to be small values so they can be cloned to every caller input + // If the scope node value node has already been inserted, the other nodes will map to it + None => TaggedValue::None, }; + let Some(input) = self.get_input_from_absolute_connector(&traversal_input) else { + return; + }; + *input = NodeInput::Value { + tagged_value: scope_node_value.into(), + exposed: false, + } + } + + let Some(input) = self.get_input_from_absolute_connector(&traversal_input) else { + return; + }; + + // This input can be called by an export, protonode input, or document node input + match input { + NodeInput::Node { node_id, output_index, .. } => { + let upstream_node_id = *node_id; + let output_index = *output_index; + let mut upstream_node_path = network_path.clone(); + upstream_node_path.push(upstream_node_id); + let Some(nested_network) = self.nested_network(network_path) else { + log::error!("traversal_input network does not exist, path {:?}", network_path); + return; + }; + let Some(upstream_document_node) = nested_network.nodes.get(&upstream_node_id) else { + log::error!("The node which was supposed to be flattened does not exist in the network, id {upstream_node_id}"); + return; + }; + + match &upstream_document_node.implementation { + DocumentNodeImplementation::Network(_node_network) => { + let traversal_input = AbsoluteInputConnector { + network_path: upstream_node_path.clone(), + connector: InputConnector::Export(output_index), + }; + self.traverse_input(protonetwork, value_connector, protonode_path, calling_protonodes, protonode_indices, traversal_input, traversal_start); + } + DocumentNodeImplementation::ProtoNode(protonode_id) => { + // Only insert the protonode if it has not previously been inserted + // Do not insert the protonode into the proto network or traverse over inputs if its already visited + let reached_protonode_index = match protonode_indices.get(&upstream_node_path) { + // The protonode has already been inserted, return its index + Some(reached_protonode_index) => *reached_protonode_index, + // Insert the protonode and traverse over inputs + None => { + let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs { + identifier: protonode_id.clone(), + inputs: vec![NodeId(0); upstream_document_node.inputs.len()], + }); + let protonode = ProtoNode { + construction_args, + // All protonodes take Context by default + input: concrete!(Context), + original_location: OriginalLocation { + protonode_path: upstream_node_path.clone().into(), + send_types_to_editor: true, + }, + stable_node_id: NodeId(0), + }; + let new_protonode_index = protonetwork.len(); + protonode_indices.insert(upstream_node_path.clone(), new_protonode_index); + protonetwork.push(protonode); + value_connector.push(None); + protonode_path.push(Some(upstream_node_path.into_boxed_slice())); + // Iterate over all upstream inputs, which will map the inputs to the index of the connected protonode + for input_index in 0..upstream_document_node.inputs.len() { + self.traverse_input( + protonetwork, + value_connector, + protonode_path, + calling_protonodes, + protonode_indices, + AbsoluteInputConnector { + network_path: network_path.clone(), + connector: InputConnector::node(upstream_node_id, input_index), + }, + (new_protonode_index, input_index), + ); + } + new_protonode_index + } + }; + calling_protonodes.entry(reached_protonode_index).or_insert_with(Vec::new).push(traversal_start); + } + DocumentNodeImplementation::Extract => todo!(), + } + } + NodeInput::Value { tagged_value, .. } => { + // Deduplication of value nodes based on their tagged value, since they do not depend on the Context + // + use std::hash::Hasher; + let mut hasher = rustc_hash::FxHasher::default(); + tagged_value.hash(&mut hasher); + let value_node_path = vec![NodeId(hasher.finish())]; + + // Only insert the value protonode if it has not previously been inserted + let value_protonode_index = match protonode_indices.get(&value_node_path) { + // The value input has already been inserted, return it the existing value nodes index + Some(value_protonode_index) => *value_protonode_index, + // Insert the protonode and traverse over inputs + None => { + let protonode = ProtoNode { + construction_args: ConstructionArgs::Value(std::mem::replace(tagged_value, TaggedValue::None.into())), + input: concrete!(Context), // Could be () + original_location: OriginalLocation { + protonode_path: Vec::new().into(), + send_types_to_editor: false, + }, + stable_node_id: NodeId(0), + }; + let new_protonode_index = protonetwork.len(); + protonode_indices.insert(value_node_path.clone(), new_protonode_index); + protonetwork.push(protonode); + value_connector.push(Some(traversal_input)); + protonode_path.push(None); + new_protonode_index + } + }; + calling_protonodes.entry(value_protonode_index).or_insert_with(Vec::new).push(traversal_start); + } + // Continue traversal + NodeInput::Network { import_index, .. } => { + let mut encapsulating_network_path = network_path.clone(); + let node_id = encapsulating_network_path.pop().unwrap(); + let traversal_input = AbsoluteInputConnector { + network_path: encapsulating_network_path, + connector: InputConnector::node(node_id, *import_index), + }; + self.traverse_input(protonetwork, value_connector, protonode_path, calling_protonodes, protonode_indices, traversal_input, traversal_start); + } + NodeInput::Scope(_cow) => unreachable!(), + NodeInput::Reflection(_document_node_metadata) => unreachable!(), + NodeInput::Inline(_inline_rust) => todo!(), } } - // /// Locate the export that is a [`NodeInput::Network`] at index `offset` and replace it with a [`NodeInput::Node`]. - // fn populate_first_network_export(&mut self, node: &mut DocumentNode, node_id: NodeId, output_index: usize, lambda: bool, export_index: usize, source: impl Iterator, skip: usize) { - // self.exports[export_index] = NodeInput::Node { node_id, output_index, lambda }; - // let input_source = &mut node.original_location.inputs_source; - // for source in source { - // input_source.insert(source, output_index + node.original_location.skip_inputs - skip); + // pub fn collect_downstream_metadata( + // reached_protonode_index: usize, + // calling_protonodes: &mut HashMap>, + // protonode_indices: &mut HashMap, usize>, + // downstream_calling_inputs: Vec, + // ) { + // // Map the first downstream calling node input (which is traversed for every node input) to the reached protonode + // let downstream_protonode_caller = downstream_calling_inputs[0].clone(); + + // match &downstream_protonode_caller.connector { + // InputConnector::Node { node_id, input_index } => { + // // The calling protonode has already been added to the flattened network, so it can be looked up by index and the reached node can be mapped to it + // let mut calling_protonode_path = downstream_protonode_caller.network_path.clone(); + // calling_protonode_path.push(*node_id); + // let calling_protonode_index = protonode_indices[&calling_protonode_path]; + + // } + // InputConnector::Export(_) => {} // } // } - fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> { - let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone(); - if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation { - if ident.name == "graphene_core::ops::IdentityNode" { - assert_eq!(node.inputs.len(), 1, "Id node has more than one input"); - if let NodeInput::Node { node_id, output_index, .. } = node.inputs[0] { - let node_input_output_index = output_index; - // TODO fix - if let Some(input_node) = self.nodes.get_mut(&node_id) { - for &dep in &node.original_location.dependants[0] { - input_node.original_location.dependants[output_index].push(dep); - } - } - - let input_node_id = node_id; - for output in self.nodes.values_mut() { - for (index, input) in output.inputs.iter_mut().enumerate() { - if let NodeInput::Node { - node_id: output_node_id, - output_index: output_output_index, - .. - } = input - { - if *output_node_id == id { - *output_node_id = input_node_id; - *output_output_index = node_input_output_index; - - let input_source = &mut output.original_location.inputs_source; - for source in node.original_location.inputs(index) { - input_source.insert(source, index + output.original_location.skip_inputs - node.original_location.skip_inputs); - } - } - } - } - for node_input in self.exports.iter_mut() { - if let NodeInput::Node { node_id, output_index, .. } = node_input { - if *node_id == id { - *node_id = input_node_id; - *output_index = node_input_output_index; - } - } - } - } - } - self.nodes.remove(&id); - } - } - Ok(()) - } - - /// Strips out any [`graphene_core::ops::IdentityNode`]s that are unnecessary. - pub fn remove_redundant_id_nodes(&mut self) { - let id_nodes = self - .nodes - .iter() - .filter(|(_, node)| { - matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")) - && node.inputs.len() == 1 - && matches!(node.inputs[0], NodeInput::Node { .. }) - }) - .map(|(id, _)| *id) - .collect::>(); - for id in id_nodes { - if let Err(e) = self.remove_id_node(id) { - log::warn!("{e}") - } - } - } - /// Converts the `DocumentNode`s with a `DocumentNodeImplementation::Extract` into a `ClonedNode` that returns /// the `DocumentNode` specified by the single `NodeInput::Node`. /// The referenced node is removed from the network, and any `NodeInput::Node`s used by the referenced node are replaced with a generically typed network input. pub fn resolve_extract_nodes(&mut self) { - let mut extraction_nodes = self - .nodes - .iter() - .filter(|(_, node)| matches!(node.implementation, DocumentNodeImplementation::Extract)) - .map(|(id, node)| (*id, node.clone())) - .collect::>(); - self.nodes.retain(|_, node| !matches!(node.implementation, DocumentNodeImplementation::Extract)); + // let mut extraction_nodes = self + // .nodes + // .iter() + // .filter(|(_, node)| matches!(node.implementation, DocumentNodeImplementation::Extract)) + // .map(|(id, node)| (*id, node.clone())) + // .collect::>(); + // self.nodes.retain(|_, node| !matches!(node.implementation, DocumentNodeImplementation::Extract)); - for (_, node) in &mut extraction_nodes { - assert_eq!(node.inputs.len(), 1); - let NodeInput::Node { node_id, output_index, .. } = node.inputs.pop().unwrap() else { - panic!("Extract node has no input, inputs: {:?}", node.inputs); - }; - assert_eq!(output_index, 0); - // TODO: check if we can read lambda checking? - let mut input_node = self.nodes.remove(&node_id).unwrap(); - node.implementation = DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()); - if let Some(input) = input_node.inputs.get_mut(0) { - *input = match &input { - NodeInput::Node { .. } => NodeInput::network(generic!(T), 0), - ni => NodeInput::network(ni.ty(), 0), - }; - } + // for (_, node) in &mut extraction_nodes { + // assert_eq!(node.inputs.len(), 1); + // let NodeInput::Node { node_id, output_index, .. } = node.inputs.pop().unwrap() else { + // panic!("Extract node has no input, inputs: {:?}", node.inputs); + // }; + // assert_eq!(output_index, 0); + // // TODO: check if we can read lambda checking? + // let mut input_node = self.nodes.remove(&node_id).unwrap(); + // node.implementation = DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()); + // if let Some(input) = input_node.inputs.get_mut(0) { + // *input = match &input { + // NodeInput::Node { .. } => NodeInput::network(generic!(T), 0), + // ni => NodeInput::network(ni.ty(), 0), + // }; + // } - for input in input_node.inputs.iter_mut() { - if let NodeInput::Node { .. } = input { - *input = NodeInput::network(generic!(T), 0) - } - } - node.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node), false)]; - } - self.nodes.extend(extraction_nodes); - } - - /// Creates a proto network for evaluating each output of this network. - pub fn into_proto_networks(self) -> impl Iterator { - let nodes: Vec<_> = self.nodes.into_iter().map(|(id, node)| (id, node.resolve_proto_node())).collect(); - - // Create a network to evaluate each output - if self.exports.len() == 1 { - if let NodeInput::Node { node_id, .. } = self.exports[0] { - return vec![ProtoNetwork { - inputs: Vec::new(), - output: node_id, - nodes, - }] - .into_iter(); - } - } - - // Create a network to evaluate each output - let networks: Vec<_> = self - .exports - .into_iter() - .filter_map(move |output| { - if let NodeInput::Node { node_id, .. } = output { - Some(ProtoNetwork { - inputs: Vec::new(), // Inputs field is not used. Should be deleted - // inputs: vec![input_node.expect("Set node should always exist")], - // inputs: self.imports.clone(), - output: node_id, - nodes: nodes.clone(), - }) - } else { - None - } - }) - .collect(); - networks.into_iter() + // for input in input_node.inputs.iter_mut() { + // if let NodeInput::Node { .. } = input { + // *input = NodeInput::network(generic!(T), 0) + // } + // } + // node.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node), false)]; + // } + // self.nodes.extend(extraction_nodes); } /// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested. pub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> { - let nodes = self.nodes.iter().map(|(id, node)| (id, node, Vec::new())).collect(); + let nodes = self.nodes.iter().map(|(path, node)| (vec![*path], node)).collect(); RecursiveNodeIter { nodes } } } +#[derive(Debug)] +pub struct CompilationMetadata { + // Stored for every value input in the compiled network + pub protonode_callers_for_value: Vec<(AbsoluteInputConnector, CompiledProtonodeInput)>, + // Stored for every protonode in the compiled network + pub protonode_callers_for_node: Vec<(ProtonodePath, CompiledProtonodeInput)>, + pub types_to_add: Vec<(SNI, Vec)>, + pub types_to_remove: Vec<(SNI, usize)>, +} + +//An Input connector with a node path for unique identification +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct AbsoluteInputConnector { + pub network_path: Vec, + pub connector: InputConnector, +} + +impl AbsoluteInputConnector { + pub fn traversal_start() -> Self { + AbsoluteInputConnector { + network_path: Vec::new(), + connector: InputConnector::Export(0), + } + } +} +/// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum InputConnector { + #[serde(rename = "node")] + Node { + #[serde(rename = "nodeId")] + node_id: NodeId, + #[serde(rename = "inputIndex")] + input_index: usize, + }, + #[serde(rename = "export")] + Export(usize), +} + +impl Default for InputConnector { + fn default() -> Self { + InputConnector::Export(0) + } +} + +impl InputConnector { + pub fn node(node_id: NodeId, input_index: usize) -> Self { + InputConnector::Node { node_id, input_index } + } + + pub fn input_index(&self) -> usize { + match self { + InputConnector::Node { input_index, .. } => *input_index, + InputConnector::Export(input_index) => *input_index, + } + } + + pub fn node_id(&self) -> Option { + match self { + InputConnector::Node { node_id, .. } => Some(*node_id), + _ => None, + } + } +} + +//An Output connector with a node path for unique identification +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct AbsoluteOutputConnector { + pub path: Vec, + pub connector: OutputConnector, +} + +/// Represents an output connector +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum OutputConnector { + #[serde(rename = "node")] + Node { + #[serde(rename = "nodeId")] + node_id: NodeId, + #[serde(rename = "outputIndex")] + output_index: usize, + }, + #[serde(rename = "import")] + Import(usize), +} + +impl Default for OutputConnector { + fn default() -> Self { + OutputConnector::Import(0) + } +} + +impl OutputConnector { + pub fn node(node_id: NodeId, output_index: usize) -> Self { + OutputConnector::Node { node_id, output_index } + } + + pub fn index(&self) -> usize { + match self { + OutputConnector::Node { output_index, .. } => *output_index, + OutputConnector::Import(output_index) => *output_index, + } + } + + pub fn node_id(&self) -> Option { + match self { + OutputConnector::Node { node_id, .. } => Some(*node_id), + _ => None, + } + } + + pub fn from_input(input: &NodeInput) -> Option { + match input { + NodeInput::Network { import_index, .. } => Some(Self::Import(*import_index)), + NodeInput::Node { node_id, output_index, .. } => Some(Self::node(*node_id, *output_index)), + _ => None, + } + } +} + /// An iterator over all [`DocumentNode`]s, including ones that are deeply nested. pub struct RecursiveNodeIter<'a> { - nodes: Vec<(&'a NodeId, &'a DocumentNode, Vec)>, + nodes: Vec<(Vec, &'a DocumentNode)>, } impl<'a> Iterator for RecursiveNodeIter<'a> { - type Item = (&'a NodeId, &'a DocumentNode, Vec); + type Item = (Vec, &'a DocumentNode); fn next(&mut self) -> Option { - let (current_id, node, path) = self.nodes.pop()?; + let (path, node) = self.nodes.pop()?; if let DocumentNodeImplementation::Network(network) = &node.implementation { - self.nodes.extend(network.nodes.iter().map(|(id, node)| { - let mut nested_path = path.clone(); - nested_path.push(*current_id); - (id, node, nested_path) - })); + for (node_id, node) in &network.nodes { + let mut new_path = path.to_vec(); + new_path.push(*node_id); + self.nodes.push((new_path, node)); + } } - Some((current_id, node, path)) + Some((path, node)) } } #[cfg(test)] -mod test { - use super::*; - use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; - use std::sync::atomic::AtomicU64; +// mod test { +// use super::*; +// use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; +// use std::sync::atomic::AtomicU64; - fn gen_node_id() -> NodeId { - static NODE_ID: AtomicU64 = AtomicU64::new(4); - NodeId(NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst)) - } +// fn gen_node_id() -> NodeId { +// static NODE_ID: AtomicU64 = AtomicU64::new(4); +// NodeId(NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst)) +// } - fn add_network() -> NodeNetwork { - NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], - nodes: [ - ( - NodeId(0), - DocumentNode { - inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(u32), 1)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), - ..Default::default() - }, - ), - ( - NodeId(1), - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), - ..Default::default() - }, - ), - ] - .into_iter() - .collect(), - ..Default::default() - } - } +// fn add_network() -> NodeNetwork { +// NodeNetwork { +// exports: vec![NodeInput::node(NodeId(1), 0)], +// nodes: [ +// ( +// NodeId(0), +// DocumentNode { +// inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(u32), 1)], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), +// ..Default::default() +// }, +// ), +// ( +// NodeId(1), +// DocumentNode { +// inputs: vec![NodeInput::node(NodeId(0), 0)], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), +// ..Default::default() +// }, +// ), +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// } +// } - #[test] - fn map_ids() { - let mut network = add_network(); - network.map_ids(|id| NodeId(id.0 + 1)); - let mapped_add = NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], - nodes: [ - ( - NodeId(1), - DocumentNode { - inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(u32), 1)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), - ..Default::default() - }, - ), - ( - NodeId(2), - DocumentNode { - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), - ..Default::default() - }, - ), - ] - .into_iter() - .collect(), - ..Default::default() - }; - assert_eq!(network, mapped_add); - } +// #[test] +// fn map_ids() { +// let mut network = add_network(); +// network.map_ids(|id| NodeId(id.0 + 1)); +// let mapped_add = NodeNetwork { +// exports: vec![NodeInput::node(NodeId(2), 0)], +// nodes: [ +// ( +// NodeId(1), +// DocumentNode { +// inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(u32), 1)], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), +// ..Default::default() +// }, +// ), +// ( +// NodeId(2), +// DocumentNode { +// inputs: vec![NodeInput::node(NodeId(1), 0)], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), +// ..Default::default() +// }, +// ), +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// }; +// assert_eq!(network, mapped_add); +// } - #[test] - fn extract_node() { - let id_node = DocumentNode { - inputs: vec![], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()), - ..Default::default() - }; - // TODO: Extend test cases to test nested network - let mut extraction_network = NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], - nodes: [ - id_node.clone(), - DocumentNode { - inputs: vec![NodeInput::lambda(NodeId(0), 0)], - implementation: DocumentNodeImplementation::Extract, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }; - extraction_network.resolve_extract_nodes(); - assert_eq!(extraction_network.nodes.len(), 1); - let inputs = extraction_network.nodes.get(&NodeId(1)).unwrap().inputs.clone(); - assert_eq!(inputs.len(), 1); - assert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(network), ..) if network == &id_node)); - } +// #[test] +// fn extract_node() { +// let id_node = DocumentNode { +// inputs: vec![], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()), +// ..Default::default() +// }; +// // TODO: Extend test cases to test nested network +// let mut extraction_network = NodeNetwork { +// exports: vec![NodeInput::node(NodeId(1), 0)], +// nodes: [ +// id_node.clone(), +// DocumentNode { +// inputs: vec![NodeInput::lambda(NodeId(0), 0)], +// implementation: DocumentNodeImplementation::Extract, +// ..Default::default() +// }, +// ] +// .into_iter() +// .enumerate() +// .map(|(id, node)| (NodeId(id as u64), node)) +// .collect(), +// ..Default::default() +// }; +// extraction_network.resolve_extract_nodes(); +// assert_eq!(extraction_network.nodes.len(), 1); +// let inputs = extraction_network.nodes.get(&NodeId(1)).unwrap().inputs.clone(); +// assert_eq!(inputs.len(), 1); +// assert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(network), ..) if network == &id_node)); +// } - #[test] - fn flatten_add() { - let mut network = NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], - nodes: [( - NodeId(1), - DocumentNode { - inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::value(TaggedValue::U32(2), false)], - implementation: DocumentNodeImplementation::Network(add_network()), - ..Default::default() - }, - )] - .into_iter() - .collect(), - ..Default::default() - }; - network.populate_dependants(); - network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), gen_node_id); - let flat_network = flat_network(); - println!("{flat_network:#?}"); - println!("{network:#?}"); +// #[test] +// fn flatten_add() { +// let mut network = NodeNetwork { +// exports: vec![NodeInput::node(NodeId(1), 0)], +// nodes: [( +// NodeId(1), +// DocumentNode { +// inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::value(TaggedValue::U32(2), false)], +// implementation: DocumentNodeImplementation::Network(add_network()), +// ..Default::default() +// }, +// )] +// .into_iter() +// .collect(), +// ..Default::default() +// }; +// network.populate_dependants(); +// network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), gen_node_id); +// let flat_network = flat_network(); +// println!("{flat_network:#?}"); +// println!("{network:#?}"); - assert_eq!(flat_network, network); - } +// assert_eq!(flat_network, network); +// } - #[test] - fn resolve_proto_node_add() { - let document_node = DocumentNode { - inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), - ..Default::default() - }; +// #[test] +// fn resolve_proto_node_add() { +// let document_node = DocumentNode { +// inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(0), 0)], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), +// ..Default::default() +// }; - let proto_node = document_node.resolve_proto_node(); - let reference = ProtoNode { - identifier: "graphene_core::structural::ConsNode".into(), - input: ProtoNodeInput::ManualComposition(concrete!(u32)), - construction_args: ConstructionArgs::Nodes(vec![(NodeId(0), false)]), - ..Default::default() - }; - assert_eq!(proto_node, reference); - } +// let proto_node = document_node.resolve_proto_node(); +// let reference = ProtoNode { +// construction_args: ConstructionArgs::Nodes(NodeConstructionArgs { identifier: "graphene_core::structural::ConsNode".into(), inputs: vec![(NodeId(0), false)]}), +// ..Default::default() +// }; +// assert_eq!(proto_node, reference); +// } - #[test] - fn resolve_flatten_add_as_proto_network() { - let construction_network = ProtoNetwork { - inputs: Vec::new(), - output: NodeId(11), - nodes: [ - ( - NodeId(10), - ProtoNode { - identifier: "graphene_core::structural::ConsNode".into(), - input: ProtoNodeInput::ManualComposition(concrete!(u32)), - construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]), - original_location: OriginalLocation { - path: Some(vec![NodeId(1), NodeId(0)]), - inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(), - inputs_exposed: vec![true, true], - skip_inputs: 0, - ..Default::default() - }, +// #[test] +// fn resolve_flatten_add_as_proto_network() { +// let construction_network = ProtoNetwork { +// output: NodeId(11), +// nodes: [ +// ( +// NodeId(10), +// ProtoNode { +// identifier: "graphene_core::structural::ConsNode".into(), +// construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]), +// // document_node_path: OriginalLocation { +// // path: Some(vec![NodeId(1), NodeId(0)]), +// // inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(), +// // inputs_exposed: vec![true, true], +// // skip_inputs: 0, +// // ..Default::default() +// // }, +// ..Default::default() +// }, +// ), +// ( +// NodeId(11), +// ProtoNode { +// identifier: "graphene_core::ops::AddPairNode".into(), +// construction_args: ConstructionArgs::Nodes(vec![]), +// // document_node_path: OriginalLocation { +// // path: Some(vec![NodeId(1), NodeId(1)]), +// // inputs_source: HashMap::new(), +// // inputs_exposed: vec![true], +// // skip_inputs: 0, +// // ..Default::default() +// // }, +// ..Default::default() +// }, +// ), +// ( +// NodeId(14), +// ProtoNode { +// identifier: "graphene_core::value::ClonedNode".into(), +// construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()), +// // document_node_path: OriginalLocation { +// // path: Some(vec![NodeId(1), NodeId(4)]), +// // inputs_source: HashMap::new(), +// // inputs_exposed: vec![true, false], +// // skip_inputs: 0, +// // ..Default::default() +// // }, +// ..Default::default() +// }, +// ), +// ] +// .into_iter() +// .collect(), +// }; +// let network = flat_network(); +// let mut resolved_network = network.into_proto_network().collect::>(); +// resolved_network[0].nodes.sort_unstable_by_key(|(id, _)| *id); - ..Default::default() - }, - ), - ( - NodeId(11), - ProtoNode { - identifier: "graphene_core::ops::AddPairNode".into(), - input: ProtoNodeInput::Node(NodeId(10)), - construction_args: ConstructionArgs::Nodes(vec![]), - original_location: OriginalLocation { - path: Some(vec![NodeId(1), NodeId(1)]), - inputs_source: HashMap::new(), - inputs_exposed: vec![true], - skip_inputs: 0, - ..Default::default() - }, - ..Default::default() - }, - ), - ( - NodeId(14), - ProtoNode { - identifier: "graphene_core::value::ClonedNode".into(), - input: ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context)), - construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()), - original_location: OriginalLocation { - path: Some(vec![NodeId(1), NodeId(4)]), - inputs_source: HashMap::new(), - inputs_exposed: vec![true, false], - skip_inputs: 0, - ..Default::default() - }, - ..Default::default() - }, - ), - ] - .into_iter() - .collect(), - }; - let network = flat_network(); - let mut resolved_network = network.into_proto_networks().collect::>(); - resolved_network[0].nodes.sort_unstable_by_key(|(id, _)| *id); +// println!("{:#?}", resolved_network[0]); +// println!("{construction_network:#?}"); +// pretty_assertions::assert_eq!(resolved_network[0], construction_network); +// } - println!("{:#?}", resolved_network[0]); - println!("{construction_network:#?}"); - pretty_assertions::assert_eq!(resolved_network[0], construction_network); - } +// fn flat_network() -> NodeNetwork { +// NodeNetwork { +// exports: vec![NodeInput::node(NodeId(11), 0)], +// nodes: [ +// ( +// NodeId(10), +// DocumentNode { +// inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(14), 0)], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), +// // document_node_path: OriginalLocation { +// // path: Some(vec![NodeId(1), NodeId(0)]), +// // inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(), +// // inputs_exposed: vec![true, true], +// // skip_inputs: 0, +// // ..Default::default() +// // }, +// ..Default::default() +// }, +// ), +// ( +// NodeId(14), +// DocumentNode { +// inputs: vec![NodeInput::value(TaggedValue::U32(2), false)], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()), +// // document_node_path: OriginalLocation { +// // path: Some(vec![NodeId(1), NodeId(4)]), +// // inputs_source: HashMap::new(), +// // inputs_exposed: vec![true, false], +// // skip_inputs: 0, +// // ..Default::default() +// // }, +// ..Default::default() +// }, +// ), +// ( +// NodeId(11), +// DocumentNode { +// inputs: vec![NodeInput::node(NodeId(10), 0)], +// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), +// // document_node_path: OriginalLocation { +// // path: Some(vec![NodeId(1), NodeId(1)]), +// // inputs_source: HashMap::new(), +// // inputs_exposed: vec![true], +// // skip_inputs: 0, +// // ..Default::default() +// // }, +// ..Default::default() +// }, +// ), +// ] +// .into_iter() +// .collect(), +// ..Default::default() +// } +// } - fn flat_network() -> NodeNetwork { - NodeNetwork { - exports: vec![NodeInput::node(NodeId(11), 0)], - nodes: [ - ( - NodeId(10), - DocumentNode { - inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(14), 0)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), - original_location: OriginalLocation { - path: Some(vec![NodeId(1), NodeId(0)]), - inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(), - inputs_exposed: vec![true, true], - skip_inputs: 0, - ..Default::default() - }, - ..Default::default() - }, - ), - ( - NodeId(14), - DocumentNode { - inputs: vec![NodeInput::value(TaggedValue::U32(2), false)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()), - original_location: OriginalLocation { - path: Some(vec![NodeId(1), NodeId(4)]), - inputs_source: HashMap::new(), - inputs_exposed: vec![true, false], - skip_inputs: 0, - ..Default::default() - }, - ..Default::default() - }, - ), - ( - NodeId(11), - DocumentNode { - inputs: vec![NodeInput::node(NodeId(10), 0)], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), - original_location: OriginalLocation { - path: Some(vec![NodeId(1), NodeId(1)]), - inputs_source: HashMap::new(), - inputs_exposed: vec![true], - skip_inputs: 0, - ..Default::default() - }, - ..Default::default() - }, - ), - ] - .into_iter() - .collect(), - ..Default::default() - } - } - - fn two_node_identity() -> NodeNetwork { - NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], - nodes: [ - ( - NodeId(1), - DocumentNode { - inputs: vec![NodeInput::network(concrete!(u32), 0)], - implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), - ..Default::default() - }, - ), - ( - NodeId(2), - DocumentNode { - inputs: vec![NodeInput::network(concrete!(u32), 1)], - implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), - ..Default::default() - }, - ), - ] - .into_iter() - .collect(), - ..Default::default() - } - } - - fn output_duplicate(network_outputs: Vec, result_node_input: NodeInput) -> NodeNetwork { - let mut network = NodeNetwork { - exports: network_outputs, - nodes: [ - ( - NodeId(1), - DocumentNode { - inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)], - implementation: DocumentNodeImplementation::Network(two_node_identity()), - ..Default::default() - }, - ), - ( - NodeId(2), - DocumentNode { - inputs: vec![result_node_input], - implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), - ..Default::default() - }, - ), - ] - .into_iter() - .collect(), - ..Default::default() - }; - let _new_ids = 101..; - network.populate_dependants(); - network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10000)); - network.flatten_with_fns(NodeId(2), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10001)); - network.remove_dead_nodes(0); - network - } - - #[test] - fn simple_duplicate() { - let result = output_duplicate(vec![NodeInput::node(NodeId(1), 0)], NodeInput::node(NodeId(1), 0)); - println!("{result:#?}"); - assert_eq!(result.exports.len(), 1, "The number of outputs should remain as 1"); - assert_eq!(result.exports[0], NodeInput::node(NodeId(11), 0), "The outer network output should be from a duplicated inner network"); - let mut ids = result.nodes.keys().copied().collect::>(); - ids.sort(); - assert_eq!(ids, vec![NodeId(11), NodeId(10010)], "Should only contain identity and values"); - } - - // TODO: Write more tests - // #[test] - // fn out_of_order_duplicate() { - // let result = output_duplicate(vec![NodeInput::node(NodeId(10), 1), NodeInput::node(NodeId(10), 0)], NodeInput::node(NodeId(10), 0); - // assert_eq!( - // result.outputs[0], - // NodeInput::node(NodeId(101), 0), - // "The first network output should be from a duplicated nested network" - // ); - // assert_eq!( - // result.outputs[1], - // NodeInput::node(NodeId(10), 0), - // "The second network output should be from the original nested network" - // ); - // assert!( - // result.nodes.contains_key(&NodeId(10)) && result.nodes.contains_key(&NodeId(101)) && result.nodes.len() == 2, - // "Network should contain two duplicated nodes" - // ); - // for (node_id, input_value, inner_id) in [(10, 1., 1), (101, 2., 2)] { - // let nested_network_node = result.nodes.get(&NodeId(node_id)).unwrap(); - // assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change"); - // assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(input_value), false)], "Input should be stable"); - // let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network"); - // assert_eq!(inner_network.inputs, vec![inner_id], "The input should be sent to the second node"); - // assert_eq!(inner_network.outputs, vec![NodeInput::node(NodeId(inner_id), 0)], "The output should be node id"); - // assert_eq!(inner_network.nodes.get(&NodeId(inner_id)).unwrap().name, format!("Identity {inner_id}"), "The node should be identity"); + // fn two_node_identity() -> NodeNetwork { + // NodeNetwork { + // exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], + // nodes: [ + // ( + // NodeId(1), + // DocumentNode { + // inputs: vec![NodeInput::network(concrete!(u32), 0)], + // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), + // ..Default::default() + // }, + // ), + // ( + // NodeId(2), + // DocumentNode { + // inputs: vec![NodeInput::network(concrete!(u32), 1)], + // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), + // ..Default::default() + // }, + // ), + // ] + // .into_iter() + // .collect(), + // ..Default::default() // } // } - // #[test] - // fn using_other_node_duplicate() { - // let result = output_duplicate(vec![NodeInput::node(NodeId(11), 0)], NodeInput::node(NodeId(10), 1); - // assert_eq!(result.outputs, vec![NodeInput::node(NodeId(11), 0)], "The network output should be the result node"); - // assert!( - // result.nodes.contains_key(&NodeId(11)) && result.nodes.contains_key(&NodeId(101)) && result.nodes.len() == 2, - // "Network should contain a duplicated node and a result node" - // ); - // let result_node = result.nodes.get(&NodeId(11)).unwrap(); - // assert_eq!(result_node.inputs, vec![NodeInput::node(NodeId(101), 0)], "Result node should refer to duplicate node as input"); - // let nested_network_node = result.nodes.get(&NodeId(101)).unwrap(); - // assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change"); - // assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(2.), false)], "Input should be 2"); - // let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network"); - // assert_eq!(inner_network.inputs, vec![2], "The input should be sent to the second node"); - // assert_eq!(inner_network.outputs, vec![NodeInput::node(NodeId(2), 0)], "The output should be node id 2"); - // assert_eq!(inner_network.nodes.get(&NodeId(2)).unwrap().name, "Identity 2", "The node should be identity 2"); + + // fn output_duplicate(network_outputs: Vec, result_node_input: NodeInput) -> NodeNetwork { + // let mut network = NodeNetwork { + // exports: network_outputs, + // nodes: [ + // ( + // NodeId(1), + // DocumentNode { + // inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)], + // implementation: DocumentNodeImplementation::Network(two_node_identity()), + // ..Default::default() + // }, + // ), + // ( + // NodeId(2), + // DocumentNode { + // inputs: vec![result_node_input], + // implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), + // ..Default::default() + // }, + // ), + // ] + // .into_iter() + // .collect(), + // ..Default::default() + // }; + // let _new_ids = 101..; + // network.populate_dependants(); + // network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10000)); + // network.flatten_with_fns(NodeId(2), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10001)); + // network.remove_dead_nodes(0); + // network // } -} + +// #[test] +// fn simple_duplicate() { +// let result = output_duplicate(vec![NodeInput::node(NodeId(1), 0)], NodeInput::node(NodeId(1), 0)); +// println!("{result:#?}"); +// assert_eq!(result.exports.len(), 1, "The number of outputs should remain as 1"); +// assert_eq!(result.exports[0], NodeInput::node(NodeId(11), 0), "The outer network output should be from a duplicated inner network"); +// let mut ids = result.nodes.keys().copied().collect::>(); +// ids.sort(); +// assert_eq!(ids, vec![NodeId(11), NodeId(10010)], "Should only contain identity and values"); +// } + +// // TODO: Write more tests +// // #[test] +// // fn out_of_order_duplicate() { +// // let result = output_duplicate(vec![NodeInput::node(NodeId(10), 1), NodeInput::node(NodeId(10), 0)], NodeInput::node(NodeId(10), 0); +// // assert_eq!( +// // result.outputs[0], +// // NodeInput::node(NodeId(101), 0), +// // "The first network output should be from a duplicated nested network" +// // ); +// // assert_eq!( +// // result.outputs[1], +// // NodeInput::node(NodeId(10), 0), +// // "The second network output should be from the original nested network" +// // ); +// // assert!( +// // result.nodes.contains_key(&NodeId(10)) && result.nodes.contains_key(&NodeId(101)) && result.nodes.len() == 2, +// // "Network should contain two duplicated nodes" +// // ); +// // for (node_id, input_value, inner_id) in [(10, 1., 1), (101, 2., 2)] { +// // let nested_network_node = result.nodes.get(&NodeId(node_id)).unwrap(); +// // assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change"); +// // assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(input_value), false)], "Input should be stable"); +// // let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network"); +// // assert_eq!(inner_network.inputs, vec![inner_id], "The input should be sent to the second node"); +// // assert_eq!(inner_network.outputs, vec![NodeInput::node(NodeId(inner_id), 0)], "The output should be node id"); +// // assert_eq!(inner_network.nodes.get(&NodeId(inner_id)).unwrap().name, format!("Identity {inner_id}"), "The node should be identity"); +// // } +// // } +// // #[test] +// // fn using_other_node_duplicate() { +// // let result = output_duplicate(vec![NodeInput::node(NodeId(11), 0)], NodeInput::node(NodeId(10), 1); +// // assert_eq!(result.outputs, vec![NodeInput::node(NodeId(11), 0)], "The network output should be the result node"); +// // assert!( +// // result.nodes.contains_key(&NodeId(11)) && result.nodes.contains_key(&NodeId(101)) && result.nodes.len() == 2, +// // "Network should contain a duplicated node and a result node" +// // ); +// // let result_node = result.nodes.get(&NodeId(11)).unwrap(); +// // assert_eq!(result_node.inputs, vec![NodeInput::node(NodeId(101), 0)], "Result node should refer to duplicate node as input"); +// // let nested_network_node = result.nodes.get(&NodeId(101)).unwrap(); +// // assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change"); +// // assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(2.), false)], "Input should be 2"); +// // let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network"); +// // assert_eq!(inner_network.inputs, vec![2], "The input should be sent to the second node"); +// // assert_eq!(inner_network.outputs, vec![NodeInput::node(NodeId(2), 0)], "The output should be node id 2"); +// // assert_eq!(inner_network.nodes.get(&NodeId(2)).unwrap().name, "Identity 2", "The node should be identity 2"); +// // } +// } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index c8c896290c..168e585ccc 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -108,6 +108,16 @@ macro_rules! tagged_value { _ => Err(format!("Cannot convert {:?} to TaggedValue",std::any::type_name_of_val(input))), } } + // Check for equality between a dynamic type and a tagged value without cloning + pub fn compare_value_to_dyn_any(&self, any: Box) -> bool { + match self { + TaggedValue::None => any.downcast_ref::<()>().is_some(), + $(TaggedValue::$identifier(value) => {any.downcast_ref::<$ty>().map_or(false, |v| v==value)}, )* + TaggedValue::RenderOutput(value) => any.downcast_ref::().map_or(false, |v| v==value), + TaggedValue::SurfaceFrame(value) => any.downcast_ref::().map_or(false, |v| v==value), + TaggedValue::EditorApi(value) => any.downcast_ref::>().map_or(false, |v| v==value), + } + } pub fn from_type(input: &Type) -> Option { match input { Type::Generic(_) => None, @@ -372,6 +382,18 @@ impl TaggedValue { _ => panic!("Passed value is not of type u32"), } } + + pub fn as_renderable<'a>(value: &'a TaggedValue) -> Option<&'a dyn graphene_svg_renderer::GraphicElementRendered> { + match value { + TaggedValue::VectorData(v) => Some(v), + TaggedValue::RasterData(r) => Some(r), + TaggedValue::GraphicElement(e) => Some(e), + TaggedValue::GraphicGroup(g) => Some(g), + TaggedValue::ArtboardGroup(a) => Some(a), + TaggedValue::Artboard(a) => Some(a), + _ => None, + } + } } impl Display for TaggedValue { diff --git a/node-graph/graph-craft/src/graphene_compiler.rs b/node-graph/graph-craft/src/graphene_compiler.rs index d34cc6f663..8b13789179 100644 --- a/node-graph/graph-craft/src/graphene_compiler.rs +++ b/node-graph/graph-craft/src/graphene_compiler.rs @@ -1,36 +1 @@ -use crate::document::NodeNetwork; -use crate::proto::{LocalFuture, ProtoNetwork}; -use std::error::Error; -pub struct Compiler {} - -impl Compiler { - pub fn compile(&self, mut network: NodeNetwork) -> impl Iterator> { - let node_ids = network.nodes.keys().copied().collect::>(); - network.populate_dependants(); - for id in node_ids { - network.flatten(id); - } - network.resolve_scope_inputs(); - network.remove_redundant_id_nodes(); - // network.remove_dead_nodes(0); - let proto_networks = network.into_proto_networks(); - - proto_networks.map(move |mut proto_network| { - proto_network.resolve_inputs()?; - proto_network.generate_stable_node_ids(); - Ok(proto_network) - }) - } - pub fn compile_single(&self, network: NodeNetwork) -> Result { - assert_eq!(network.exports.len(), 1, "Graph with multiple outputs not yet handled"); - let Some(proto_network) = self.compile(network).next() else { - return Err("Failed to convert graph into proto graph".to_string()); - }; - proto_network - } -} - -pub trait Executor { - fn execute(&self, input: I) -> LocalFuture<'_, Result>>; -} diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 4330976609..b10c813fa1 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -1,114 +1,99 @@ use crate::document::{InlineRust, value}; -use crate::document::{NodeId, OriginalLocation}; pub use graphene_core::registry::*; +use graphene_core::uuid::{NodeId, ProtonodePath, SNI}; use graphene_core::*; -use rustc_hash::FxHashMap; use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::fmt::Debug; use std::hash::Hash; -#[derive(Debug, Default, PartialEq, Clone, Hash, Eq, serde::Serialize, serde::Deserialize)] -/// A list of [`ProtoNode`]s, which is an intermediate step between the [`crate::document::NodeNetwork`] and the `BorrowTree` containing a single flattened network. -pub struct ProtoNetwork { - // TODO: remove this since it seems to be unused? - // Should a proto Network even allow inputs? Don't think so - pub inputs: Vec, - /// The node ID that provides the output. This node is then responsible for calling the rest of the graph. - pub output: NodeId, - /// A list of nodes stored in a Vec to allow for sorting. - pub nodes: Vec<(NodeId, ProtoNode)>, +// #[derive(Debug, Default, PartialEq, Clone, Hash, Eq, serde::Serialize, serde::Deserialize)] +// /// A list of [`ProtoNode`]s, which is an intermediate step between the [`crate::document::NodeNetwork`] and the `BorrowTree` containing a single flattened network. +// pub struct ProtoNetwork { +// // TODO: remove this since it seems to be unused? +// // Should a proto Network even allow inputs? Don't think so +// pub inputs: Vec, +// /// The node ID that provides the output. This node is then responsible for calling the rest of the graph. +// pub output: NodeId, +// /// A list of nodes stored in a Vec to allow for sorting. +// pub nodes: Vec<(NodeId, ProtoNode)>, +// } + +// impl core::fmt::Display for ProtoNetwork { +// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +// f.write_str("Proto Network with nodes: ")?; +// fn write_node(f: &mut core::fmt::Formatter<'_>, network: &ProtoNetwork, id: NodeId, indent: usize) -> core::fmt::Result { +// f.write_str(&"\t".repeat(indent))?; +// let Some((_, node)) = network.nodes.iter().find(|(node_id, _)| *node_id == id) else { +// return f.write_str("{{Unknown Node}}"); +// }; +// f.write_str("Node: ")?; +// f.write_str(&node.identifier.name)?; + +// f.write_str("\n")?; +// f.write_str(&"\t".repeat(indent))?; +// f.write_str("{\n")?; + +// f.write_str(&"\t".repeat(indent + 1))?; +// f.write_str("Input: ")?; +// match &node.input { +// ProtoNodeInput::None => f.write_str("None")?, +// ProtoNodeInput::ManualComposition(ty) => f.write_fmt(format_args!("Manual Composition (type = {ty:?})"))?, +// ProtoNodeInput::Node(_) => f.write_str("Node")?, +// ProtoNodeInput::NodeLambda(_) => f.write_str("Lambda Node")?, +// } +// f.write_str("\n")?; + +// match &node.construction_args { +// ConstructionArgs::Value(value) => { +// f.write_str(&"\t".repeat(indent + 1))?; +// f.write_fmt(format_args!("Value construction argument: {value:?}"))? +// } +// ConstructionArgs::Nodes(nodes) => { +// for id in nodes { +// write_node(f, network, id.0, indent + 1)?; +// } +// } +// ConstructionArgs::Inline(inline) => { +// f.write_str(&"\t".repeat(indent + 1))?; +// f.write_fmt(format_args!("Inline construction argument: {inline:?}"))? +// } +// } +// f.write_str(&"\t".repeat(indent))?; +// f.write_str("}\n")?; +// Ok(()) +// } + +// let id = self.output; +// write_node(f, self, id, 0) +// } +// } + +#[derive(Debug, Clone, PartialEq, Hash, Eq)] +pub struct NodeConstructionArgs { + // Used to get the constructor from the function in `node_registry.rs`. + pub identifier: ProtoNodeIdentifier, + /// A list of stable node ids used as inputs to the constructor + pub inputs: Vec, } - -impl core::fmt::Display for ProtoNetwork { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("Proto Network with nodes: ")?; - fn write_node(f: &mut core::fmt::Formatter<'_>, network: &ProtoNetwork, id: NodeId, indent: usize) -> core::fmt::Result { - f.write_str(&"\t".repeat(indent))?; - let Some((_, node)) = network.nodes.iter().find(|(node_id, _)| *node_id == id) else { - return f.write_str("{{Unknown Node}}"); - }; - f.write_str("Node: ")?; - f.write_str(&node.identifier.name)?; - - f.write_str("\n")?; - f.write_str(&"\t".repeat(indent))?; - f.write_str("{\n")?; - - f.write_str(&"\t".repeat(indent + 1))?; - f.write_str("Input: ")?; - match &node.input { - ProtoNodeInput::None => f.write_str("None")?, - ProtoNodeInput::ManualComposition(ty) => f.write_fmt(format_args!("Manual Composition (type = {ty:?})"))?, - ProtoNodeInput::Node(_) => f.write_str("Node")?, - ProtoNodeInput::NodeLambda(_) => f.write_str("Lambda Node")?, - } - f.write_str("\n")?; - - match &node.construction_args { - ConstructionArgs::Value(value) => { - f.write_str(&"\t".repeat(indent + 1))?; - f.write_fmt(format_args!("Value construction argument: {value:?}"))? - } - ConstructionArgs::Nodes(nodes) => { - for id in nodes { - write_node(f, network, id.0, indent + 1)?; - } - } - ConstructionArgs::Inline(inline) => { - f.write_str(&"\t".repeat(indent + 1))?; - f.write_fmt(format_args!("Inline construction argument: {inline:?}"))? - } - } - f.write_str(&"\t".repeat(indent))?; - f.write_str("}\n")?; - Ok(()) - } - - let id = self.output; - write_node(f, self, id, 0) - } -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq)] /// Defines the arguments used to construct the boxed node struct. This is used to call the constructor function in the `node_registry.rs` file - which is hidden behind a wall of macros. pub enum ConstructionArgs { /// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe) Value(MemoHash), - /// A list of nodes used as inputs to the constructor function in `node_registry.rs`. - /// The bool indicates whether to treat the node as lambda node. - // TODO: use a struct for clearer naming. - Nodes(Vec<(NodeId, bool)>), + Nodes(NodeConstructionArgs), /// Used for GPU computation to work around the limitations of rust-gpu. Inline(InlineRust), } impl Eq for ConstructionArgs {} -impl PartialEq for ConstructionArgs { - fn eq(&self, other: &Self) -> bool { - match (&self, &other) { - (Self::Nodes(n1), Self::Nodes(n2)) => n1 == n2, - (Self::Value(v1), Self::Value(v2)) => v1 == v2, - _ => { - use std::hash::Hasher; - let hash = |input: &Self| { - let mut hasher = rustc_hash::FxHasher::default(); - input.hash(&mut hasher); - hasher.finish() - }; - hash(self) == hash(other) - } - } - } -} - impl Hash for ConstructionArgs { fn hash(&self, state: &mut H) { core::mem::discriminant(self).hash(state); match self { Self::Nodes(nodes) => { - for node in nodes { + for node in &nodes.inputs { node.hash(state); } } @@ -122,411 +107,63 @@ impl ConstructionArgs { // TODO: what? Used in the gpu_compiler crate for something. pub fn new_function_args(&self) -> Vec { match self { - ConstructionArgs::Nodes(nodes) => nodes.iter().map(|(n, _)| format!("n{:0x}", n.0)).collect(), + ConstructionArgs::Nodes(nodes) => nodes.inputs.iter().map(|n| format!("n{:0x}", n.0)).collect(), ConstructionArgs::Value(value) => vec![value.to_primitive_string()], ConstructionArgs::Inline(inline) => vec![inline.expr.clone()], } } } -#[derive(Debug, Clone, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct OriginalLocation { + /// The original location to the document node - e.g. [grandparent_id, parent_id, node_id]. + pub protonode_path: ProtonodePath, + // // Types should not be sent for autogenerated nodes or value nodes, which are not visible and inserted during compilation + pub send_types_to_editor: bool, +} + +#[derive(Debug, Clone)] /// A proto node is an intermediate step between the `DocumentNode` and the boxed struct that actually runs the node (found in the [`BorrowTree`]). /// At different stages in the compilation process, this struct will be transformed into a reduced (more restricted) form acting as a subset of its original form, but that restricted form is still valid in the earlier stage in the compilation process before it was transformed. +// If the the protonode has ConstructionArgs::Value, then its identifier is not used, and is replaced with an UpcastNode with a value of the tagged value pub struct ProtoNode { pub construction_args: ConstructionArgs, - pub input: ProtoNodeInput, - pub identifier: ProtoNodeIdentifier, + pub input: Type, pub original_location: OriginalLocation, - pub skip_deduplication: bool, + pub stable_node_id: SNI, } impl Default for ProtoNode { fn default() -> Self { Self { - identifier: ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()), - input: ProtoNodeInput::None, - original_location: OriginalLocation::default(), - skip_deduplication: false, + input: concrete!(Context), + original_location: Default::default(), + stable_node_id: NodeId(0), } } } -/// Similar to the document node's [`crate::document::NodeInput`]. -#[derive(Debug, PartialEq, Eq, Clone, Hash, serde::Serialize, serde::Deserialize)] -pub enum ProtoNodeInput { - /// This input will be converted to `()` as the call argument. - None, - /// A ManualComposition input represents an input that opts out of being resolved through the `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated - /// result to this node. Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor. - /// - /// Say we have the network `a -> b -> c` where `c` is the output node and `a` is the input node. - /// We would expect `a` to get input from the network, `b` to get input from `a`, and `c` to get input from `b`. - /// This could be represented as `f(x) = c(b(a(x)))`. `a` is run with input `x` from the network. `b` is run with input from `a`. `c` is run with input from `b`. - /// - /// However if `b`'s input is using manual composition, this means it would instead be `f(x) = c(b(x))`. This means that `b` actually gets input from the network, and `a` is not automatically - /// executed as it would be using the default ComposeNode flow. Now `b` can use its own logic to decide when or if it wants to run `a` and how to use its output. For example, the CacheNode can - /// look up `x` in its cache and return the result, or otherwise call `a`, cache the result, and return it. - ManualComposition(Type), - /// The previous node where automatic (not manual) composition occurs when compiled. The entire network, of which the node is the output, is fed as input. - /// - /// Grayscale example: - /// - /// We're interested in receiving an input of the desaturated image data which has been fed through a grayscale filter. - /// (If we were interested in the grayscale filter itself, we would use the `NodeLambda` variant.) - Node(NodeId), - /// Unlike the `Node` variant, with `NodeLambda` we treat the connected node singularly as a lambda node while ignoring all nodes which feed into it from upstream. - /// - /// Grayscale example: - /// - /// We're interested in receiving an input of a particular image filter, such as a grayscale filter in the form of a grayscale node lambda. - /// (If we were interested in some image data that had been fed through a grayscale filter, we would use the `Node` variant.) - NodeLambda(NodeId), -} - impl ProtoNode { - /// A stable node ID is a hash of a node that should stay constant. This is used in order to remove duplicates from the graph. - /// In the case of `skip_deduplication`, the `document_node_path` is also hashed in order to avoid duplicate monitor nodes from being removed (which would make it impossible to load thumbnails). - pub fn stable_node_id(&self) -> Option { - use std::hash::Hasher; - let mut hasher = rustc_hash::FxHasher::default(); - - self.identifier.name.hash(&mut hasher); - self.construction_args.hash(&mut hasher); - if self.skip_deduplication { - self.original_location.path.hash(&mut hasher); - } - - std::mem::discriminant(&self.input).hash(&mut hasher); - match self.input { - ProtoNodeInput::None => (), - ProtoNodeInput::ManualComposition(ref ty) => { - ty.hash(&mut hasher); - } - ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher), - ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher), - }; - - Some(NodeId(hasher.finish())) - } - /// Construct a new [`ProtoNode`] with the specified construction args and a `ClonedNode` implementation. - pub fn value(value: ConstructionArgs, path: Vec) -> Self { + pub fn value(value: ConstructionArgs, path: Vec, stable_node_id: SNI) -> Self { let inputs_exposed = match &value { - ConstructionArgs::Nodes(nodes) => nodes.len() + 1, + ConstructionArgs::Nodes(nodes) => nodes.inputs.len() + 1, _ => 2, }; Self { - identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"), construction_args: value, - input: ProtoNodeInput::ManualComposition(concrete!(Context)), + input: concrete!(Context), original_location: OriginalLocation { - path: Some(path), - inputs_exposed: vec![false; inputs_exposed], - ..Default::default() + protonode_path: path.into(), + send_types_to_editor: false, }, - skip_deduplication: false, - } - } - - /// Converts all references to other node IDs into new IDs by running the specified function on them. - /// This can be used when changing the IDs of the nodes, for example in the case of generating stable IDs. - pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId, skip_lambdas: bool) { - match self.input { - ProtoNodeInput::Node(id) => self.input = ProtoNodeInput::Node(f(id)), - ProtoNodeInput::NodeLambda(id) => { - if !skip_lambdas { - self.input = ProtoNodeInput::NodeLambda(f(id)) - } - } - _ => (), - } - - if let ConstructionArgs::Nodes(ids) = &mut self.construction_args { - ids.iter_mut().filter(|(_, lambda)| !(skip_lambdas && *lambda)).for_each(|(id, _)| *id = f(*id)); - } - } - - 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:#?}"), + stable_node_id, } } } -#[derive(Clone, Copy, PartialEq)] -enum NodeState { - Unvisited, - Visiting, - Visited, -} - -impl ProtoNetwork { - fn check_ref(&self, ref_id: &NodeId, id: &NodeId) { - debug_assert!( - self.nodes.iter().any(|(check_id, _)| check_id == ref_id), - "Node id:{id} has a reference which uses node id:{ref_id} which doesn't exist in network {self:#?}" - ); - } - - #[cfg(debug_assertions)] - pub fn example() -> (Self, NodeId, ProtoNode) { - let node_id = NodeId(1); - let proto_node = ProtoNode::default(); - let proto_network = ProtoNetwork { - inputs: vec![node_id], - output: node_id, - nodes: vec![(node_id, proto_node.clone())], - }; - (proto_network, node_id, proto_node) - } - - /// Construct a hashmap containing a list of the nodes that depend on this proto network. - pub fn collect_outwards_edges(&self) -> HashMap> { - let mut edges: HashMap> = HashMap::new(); - for (id, node) in &self.nodes { - match &node.input { - ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => { - 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 { - self.check_ref(ref_id, id); - edges.entry(*ref_id).or_default().push(*id) - } - } - } - edges - } - - /// Convert all node IDs to be stable (based on the hash generated by [`ProtoNode::stable_node_id`]). - /// This function requires that the graph be topologically sorted. - pub fn generate_stable_node_ids(&mut self) { - debug_assert!(self.is_topologically_sorted()); - let outwards_edges = self.collect_outwards_edges(); - - for index in 0..self.nodes.len() { - let Some(sni) = self.nodes[index].1.stable_node_id() else { - panic!("failed to generate stable node id for node {:#?}", self.nodes[index].1); - }; - self.replace_node_id(&outwards_edges, NodeId(index as u64), sni, false); - self.nodes[index].0 = sni; - } - } - - // TODO: Remove - /// Create a hashmap with the list of nodes this proto network depends on/uses as inputs. - pub fn collect_inwards_edges(&self) -> HashMap> { - let mut edges: HashMap> = HashMap::new(); - for (id, node) in &self.nodes { - match &node.input { - ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => { - 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 { - self.check_ref(ref_id, id); - edges.entry(*id).or_default().push(*ref_id) - } - } - } - edges - } - - fn collect_inwards_edges_with_mapping(&self) -> (Vec>, FxHashMap) { - let id_map: FxHashMap<_, _> = self.nodes.iter().enumerate().map(|(idx, (id, _))| (*id, idx)).collect(); - - // Collect inwards edges using dense indices - let mut inwards_edges = vec![Vec::new(); self.nodes.len()]; - for (node_id, node) in &self.nodes { - let node_index = id_map[node_id]; - match &node.input { - ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => { - self.check_ref(ref_id, &NodeId(node_index as u64)); - inwards_edges[node_index].push(id_map[ref_id]); - } - _ => {} - } - - if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args { - for (ref_id, _) in ref_nodes { - self.check_ref(ref_id, &NodeId(node_index as u64)); - inwards_edges[node_index].push(id_map[ref_id]); - } - } - } - - (inwards_edges, id_map) - } - - /// Inserts a [`structural::ComposeNode`] for each node that has a [`ProtoNodeInput::Node`]. The compose node evaluates the first node, and then sends the result into the second node. - pub fn resolve_inputs(&mut self) -> Result<(), String> { - // Perform topological sort once - self.reorder_ids()?; - - let max_id = self.nodes.len() as u64 - 1; - - // Collect outward edges once - let outwards_edges = self.collect_outwards_edges(); - - // Iterate over nodes in topological order - for node_id in 0..=max_id { - let node_id = NodeId(node_id); - - let (_, node) = &mut self.nodes[node_id.0 as usize]; - - if let ProtoNodeInput::Node(input_node_id) = node.input { - // Create a new node that composes the current node and its input node - let compose_node_id = NodeId(self.nodes.len() as u64); - - let (_, input_node_id_proto) = &self.nodes[input_node_id.0 as usize]; - - let input = input_node_id_proto.input.clone(); - - let mut path = input_node_id_proto.original_location.path.clone(); - if let Some(path) = &mut path { - path.push(node_id); - } - - self.nodes.push(( - compose_node_id, - ProtoNode { - identifier: ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"), - construction_args: ConstructionArgs::Nodes(vec![(input_node_id, false), (node_id, true)]), - input, - original_location: OriginalLocation { path, ..Default::default() }, - skip_deduplication: false, - }, - )); - - self.replace_node_id(&outwards_edges, node_id, compose_node_id, true); - } - } - self.reorder_ids()?; - Ok(()) - } - - /// Update all of the references to a node ID in the graph with a new ID named `compose_node_id`. - fn replace_node_id(&mut self, outwards_edges: &HashMap>, node_id: NodeId, compose_node_id: NodeId, skip_lambdas: bool) { - // Update references in other nodes to use the new compose node - if let Some(referring_nodes) = outwards_edges.get(&node_id) { - for &referring_node_id in referring_nodes { - let (_, referring_node) = &mut self.nodes[referring_node_id.0 as usize]; - referring_node.map_ids(|id| if id == node_id { compose_node_id } else { id }, skip_lambdas) - } - } - - if self.output == node_id { - self.output = compose_node_id; - } - - self.inputs.iter_mut().for_each(|id| { - if *id == node_id { - *id = compose_node_id; - } - }); - } - - // Based on https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search - // This approach excludes nodes that are not connected - pub fn topological_sort(&self) -> Result<(Vec, FxHashMap), String> { - let (inwards_edges, id_map) = self.collect_inwards_edges_with_mapping(); - let mut sorted = Vec::with_capacity(self.nodes.len()); - let mut stack = vec![id_map[&self.output]]; - let mut state = vec![NodeState::Unvisited; self.nodes.len()]; - - while let Some(&node_index) = stack.last() { - match state[node_index] { - NodeState::Unvisited => { - state[node_index] = NodeState::Visiting; - for &dep_index in inwards_edges[node_index].iter().rev() { - match state[dep_index] { - NodeState::Visiting => { - return Err(format!("Cycle detected involving node {}", self.nodes[dep_index].0)); - } - NodeState::Unvisited => { - stack.push(dep_index); - } - NodeState::Visited => {} - } - } - } - NodeState::Visiting => { - stack.pop(); - state[node_index] = NodeState::Visited; - sorted.push(NodeId(node_index as u64)); - } - NodeState::Visited => { - stack.pop(); - } - } - } - - Ok((sorted, id_map)) - } - - fn is_topologically_sorted(&self) -> bool { - let mut visited = HashSet::new(); - - let inwards_edges = self.collect_inwards_edges(); - for (id, _) in &self.nodes { - for &dependency in inwards_edges.get(id).unwrap_or(&Vec::new()) { - if !visited.contains(&dependency) { - dbg!(id, dependency); - dbg!(&visited); - dbg!(&self.nodes); - return false; - } - } - visited.insert(*id); - } - true - } - - /// Sort the nodes vec so it is in a topological order. This ensures that no node takes an input from a node that is found later in the list. - fn reorder_ids(&mut self) -> Result<(), String> { - let (order, _id_map) = self.topological_sort()?; - - // // Map of node ids to their current index in the nodes vector - // let current_positions: FxHashMap<_, _> = self.nodes.iter().enumerate().map(|(pos, (id, _))| (*id, pos)).collect(); - - // // Map of node ids to their new index based on topological order - let new_positions: FxHashMap<_, _> = order.iter().enumerate().map(|(pos, id)| (self.nodes[id.0 as usize].0, pos)).collect(); - // assert_eq!(id_map, current_positions); - - // Create a new nodes vector based on the topological order - - let mut new_nodes = Vec::with_capacity(order.len()); - for (index, &id) in order.iter().enumerate() { - let mut node = std::mem::take(&mut self.nodes[id.0 as usize].1); - // Update node references to reflect the new order - node.map_ids(|id| NodeId(*new_positions.get(&id).expect("node not found in lookup table") as u64), false); - new_nodes.push((NodeId(index as u64), node)); - } - - // Update node references to reflect the new order - // new_nodes.iter_mut().for_each(|(_, node)| { - // node.map_ids(|id| *new_positions.get(&id).expect("node not found in lookup table"), false); - // }); - - // Update the nodes vector and other references - self.nodes = new_nodes; - self.inputs = self.inputs.iter().filter_map(|id| new_positions.get(id).map(|x| NodeId(*x as u64))).collect(); - self.output = NodeId(*new_positions.get(&self.output).unwrap() as u64); - - assert_eq!(order.len(), self.nodes.len()); - Ok(()) - } -} #[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum GraphErrorType { NodeNotFound(NodeId), @@ -591,9 +228,15 @@ pub struct GraphError { } impl GraphError { pub fn new(node: &ProtoNode, text: impl Into) -> Self { + let identifier = match &node.construction_args { + ConstructionArgs::Nodes(node_construction_args) => node_construction_args.identifier.name.clone(), + // Values are inserted into upcast nodes + ConstructionArgs::Value(memo_hash) => "Value Node".into(), + ConstructionArgs::Inline(inline_rust) => "Inline".into(), + }; Self { - node_path: node.original_location.path.clone().unwrap_or_default(), - identifier: node.identifier.name.clone(), + node_path: node.original_location.protonode_path.to_vec(), + identifier, error: text.into(), } } @@ -613,15 +256,17 @@ pub type GraphErrors = Vec; #[derive(Default, Clone, dyn_any::DynAny)] pub struct TypingContext { lookup: Cow<'static, HashMap>>, + monitor_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 { + pub fn new(lookup: &'static HashMap>, monitor_lookup: &'static HashMap) -> Self { Self { lookup: Cow::Borrowed(lookup), + monitor_lookup: Cow::Borrowed(monitor_lookup), ..Default::default() } } @@ -629,17 +274,17 @@ impl TypingContext { /// Updates the `TypingContext` with 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<(), GraphErrors> { - for (id, node) in network.nodes.iter() { - self.infer(*id, node)?; + pub fn update(&mut self, network: &Vec) -> Result<(), GraphErrors> { + // Update types from the most upstream nodes first + for node in network.iter().rev() { + self.infer(node.stable_node_id, node)?; } - Ok(()) } - pub fn remove_inference(&mut self, node_id: NodeId) -> Option { - self.constructor.remove(&node_id); - self.inferred.remove(&node_id) + pub fn remove_inference(&mut self, node_id: &NodeId) -> Option { + self.constructor.remove(node_id); + self.inferred.remove(node_id) } /// Returns the node constructor for a given node id. @@ -647,6 +292,11 @@ impl TypingContext { self.constructor.get(&node_id).copied() } + // Returns the monitor node constructor for a given type { + pub fn monitor_constructor(&self, monitor_type: &Type) -> Option { + self.monitor_lookup.get(monitor_type).copied() + } + /// Returns the type of a given node id if it exists pub fn type_of(&self, node_id: NodeId) -> Option<&NodeIOTypes> { self.inferred.get(&node_id) @@ -659,40 +309,33 @@ impl TypingContext { return Ok(inferred.clone()); } - let inputs = match node.construction_args { + let (inputs, id) = match node.construction_args { // If the node has a value input we can infer the return type from it ConstructionArgs::Value(ref v) => { - assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context))); + // assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context))); // TODO: This should return a reference to the value let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]); self.inferred.insert(node_id, types.clone()); return Ok(types); } // If the node has nodes as inputs we can infer the types from the node outputs - ConstructionArgs::Nodes(ref nodes) => nodes - .iter() - .map(|(id, _)| { - self.inferred - .get(id) - .ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NodeNotFound(*id))]) - .map(|node| node.ty()) - }) - .collect::, GraphErrors>>()?, - ConstructionArgs::Inline(ref inline) => vec![inline.ty.clone()], + ConstructionArgs::Nodes(ref construction_args) => { + let inputs = construction_args + .inputs + .iter() + .map(|id| { + self.inferred + .get(id) + .ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NodeNotFound(*id))]) + .map(|node| node.ty()) + }) + .collect::, GraphErrors>>()?; + (inputs, &construction_args.identifier) + } + ConstructionArgs::Inline(ref inline) => (vec![inline.ty.clone()], &*Box::new(ProtoNodeIdentifier::new("Extract"))), }; - // Get the node input type from the proto node declaration - // TODO: When removing automatic composition, rename this to just `call_argument` - let primary_input_or_call_argument = match node.input { - ProtoNodeInput::None => concrete!(()), - ProtoNodeInput::ManualComposition(ref ty) => ty.clone(), - ProtoNodeInput::Node(id) | ProtoNodeInput::NodeLambda(id) => { - let input = self.inferred.get(&id).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::InputNodeNotFound(id))])?; - input.return_value.clone() - } - }; - let using_manual_composition = matches!(node.input, ProtoNodeInput::ManualComposition(_) | ProtoNodeInput::None); - let impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?; + let impls = self.lookup.get(id).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?; if let Some(index) = inputs.iter().position(|p| { matches!(p, @@ -730,10 +373,10 @@ impl TypingContext { } } - // List of all implementations that match the input types + // List of all implementations that match the call argument type let valid_output_types = impls .keys() - .filter(|node_io| valid_type(&node_io.call_argument, &primary_input_or_call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_type(p1, p2))) + .filter(|node_io| valid_type(&node_io.call_argument, &node.input) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_type(p1, p2))) .collect::>(); // Attempt to substitute generic types with concrete types and save the list of results @@ -742,7 +385,7 @@ impl TypingContext { .map(|node_io| { let generics_lookup: Result, _> = collect_generics(node_io) .iter() - .map(|generic| check_generic(node_io, &primary_input_or_call_argument, &inputs, generic).map(|x| (generic.to_string(), x))) + .map(|generic| check_generic(node_io, &node.input, &inputs, generic).map(|x| (generic.to_string(), x))) .collect(); generics_lookup.map(|generics_lookup| { @@ -762,18 +405,13 @@ impl TypingContext { let mut best_errors = usize::MAX; let mut error_inputs = Vec::new(); for node_io in impls.keys() { - let current_errors = [&primary_input_or_call_argument] + let current_errors = [&node.input] .into_iter() .chain(&inputs) .cloned() .zip([&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned()) .enumerate() .filter(|(_, (p1, p2))| !valid_type(p1, p2)) - .map(|(index, ty)| { - let i = node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index); - let i = if using_manual_composition { i } else { i + 1 }; - (i, ty) - }) .collect::>(); if current_errors.len() < best_errors { best_errors = current_errors.len(); @@ -783,15 +421,10 @@ impl TypingContext { error_inputs.push(current_errors); } } - let inputs = [&primary_input_or_call_argument] - .into_iter() - .chain(&inputs) + let inputs = inputs.iter() .enumerate() // TODO: Make the following line's if statement conditional on being a call argument or primary input - .filter_map(|(i, t)| { - let i = if using_manual_composition { i } else { i + 1 }; - if i == 0 { None } else { Some(format!("• Input {i}: {t}")) } - }) + .map(|(i, t)| {let input_number = i + 1; format!("• Input {input_number}: {t}")}) .collect::>() .join("\n"); Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })]) @@ -818,13 +451,13 @@ impl TypingContext { return Ok(node_io.clone()); } } - let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); + let inputs = [&node.input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); let valid = valid_output_types.into_iter().cloned().collect(); Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) } _ => { - let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); + let inputs = [&node.input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::>().join(", "); let valid = valid_output_types.into_iter().cloned().collect(); Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) } @@ -880,168 +513,168 @@ fn replace_generics(types: &mut NodeIOTypes, lookup: &HashMap) { } } -#[cfg(test)] -mod test { - use super::*; - use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; - #[test] - fn topological_sort() { - let construction_network = test_network(); - let (sorted, _) = construction_network.topological_sort().expect("Error when calling 'topological_sort' on 'construction_network."); - let sorted: Vec<_> = sorted.iter().map(|x| construction_network.nodes[x.0 as usize].0).collect(); - println!("{sorted:#?}"); - assert_eq!(sorted, vec![NodeId(14), NodeId(10), NodeId(11), NodeId(1)]); - } +// #[test] +// fn topological_sort() { +// let construction_network = test_network(); +// let (sorted, _) = construction_network.topological_sort().expect("Error when calling 'topological_sort' on 'construction_network."); +// let sorted: Vec<_> = sorted.iter().map(|x| construction_network.nodes[x.0 as usize].0).collect(); +// println!("{sorted:#?}"); +// assert_eq!(sorted, vec![NodeId(14), NodeId(10), NodeId(11), NodeId(1)]); +// } - #[test] - fn topological_sort_with_cycles() { - let construction_network = test_network_with_cycles(); - let sorted = construction_network.topological_sort(); +// #[test] +// fn topological_sort_with_cycles() { +// let construction_network = test_network_with_cycles(); +// let sorted = construction_network.topological_sort(); - assert!(sorted.is_err()) - } +// assert!(sorted.is_err()) +// } - #[test] - fn id_reordering() { - let mut construction_network = test_network(); - construction_network.reorder_ids().expect("Error when calling 'reorder_ids' on 'construction_network."); - let (sorted, _) = construction_network.topological_sort().expect("Error when calling 'topological_sort' on 'construction_network."); - let sorted: Vec<_> = sorted.iter().map(|x| construction_network.nodes[x.0 as usize].0).collect(); - println!("nodes: {:#?}", construction_network.nodes); - assert_eq!(sorted, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); - let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect(); - println!("{ids:#?}"); - println!("nodes: {:#?}", construction_network.nodes); - assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value"); - assert_eq!(ids, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); - } +// #[test] +// fn id_reordering() { +// let mut construction_network = test_network(); +// construction_network.reorder_ids().expect("Error when calling 'reorder_ids' on 'construction_network."); +// let (sorted, _) = construction_network.topological_sort().expect("Error when calling 'topological_sort' on 'construction_network."); +// let sorted: Vec<_> = sorted.iter().map(|x| construction_network.nodes[x.0 as usize].0).collect(); +// println!("nodes: {:#?}", construction_network.nodes); +// assert_eq!(sorted, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); +// let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect(); +// println!("{ids:#?}"); +// println!("nodes: {:#?}", construction_network.nodes); +// assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value"); +// assert_eq!(ids, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); +// } - #[test] - fn id_reordering_idempotent() { - let mut construction_network = test_network(); - construction_network.reorder_ids().expect("Error when calling 'reorder_ids' on 'construction_network."); - construction_network.reorder_ids().expect("Error when calling 'reorder_ids' on 'construction_network."); - let (sorted, _) = construction_network.topological_sort().expect("Error when calling 'topological_sort' on 'construction_network."); - assert_eq!(sorted, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); - let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect(); - println!("{ids:#?}"); - assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value"); - assert_eq!(ids, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); - } +// #[test] +// fn id_reordering_idempotent() { +// let mut construction_network = test_network(); +// construction_network.reorder_ids().expect("Error when calling 'reorder_ids' on 'construction_network."); +// construction_network.reorder_ids().expect("Error when calling 'reorder_ids' on 'construction_network."); +// let (sorted, _) = construction_network.topological_sort().expect("Error when calling 'topological_sort' on 'construction_network."); +// assert_eq!(sorted, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); +// let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect(); +// println!("{ids:#?}"); +// assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value"); +// assert_eq!(ids, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); +// } - #[test] - fn input_resolution() { - let mut construction_network = test_network(); - construction_network.resolve_inputs().expect("Error when calling 'resolve_inputs' on 'construction_network."); - 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![(NodeId(3), false), (NodeId(4), true)])); - } +// #[test] +// fn input_resolution() { +// let mut construction_network = test_network(); +// construction_network.resolve_inputs().expect("Error when calling 'resolve_inputs' on 'construction_network."); +// 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![(NodeId(3), false), (NodeId(4), true)])); +// } - #[test] - fn stable_node_id_generation() { - let mut construction_network = test_network(); - construction_network.resolve_inputs().expect("Error when calling 'resolve_inputs' on 'construction_network."); - construction_network.generate_stable_node_ids(); - assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value"); - let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect(); - assert_eq!( - ids, - vec![ - NodeId(16997244687192517417), - NodeId(12226224850522777131), - NodeId(9162113827627229771), - NodeId(12793582657066318419), - NodeId(16945623684036608820), - NodeId(2640415155091892458) - ] - ); - } +// #[test] +// fn stable_node_id_generation() { +// let mut construction_network = test_network(); +// construction_network.resolve_inputs().expect("Error when calling 'resolve_inputs' on 'construction_network."); +// construction_network.generate_stable_node_ids(); +// assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value"); +// let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect(); +// assert_eq!( +// ids, +// vec![ +// NodeId(16997244687192517417), +// NodeId(12226224850522777131), +// NodeId(9162113827627229771), +// NodeId(12793582657066318419), +// NodeId(16945623684036608820), +// NodeId(2640415155091892458) +// ] +// ); +// } - fn test_network() -> ProtoNetwork { - ProtoNetwork { - inputs: vec![NodeId(10)], - output: NodeId(1), - nodes: [ - ( - NodeId(7), - ProtoNode { - identifier: "id".into(), - input: ProtoNodeInput::Node(NodeId(11)), - construction_args: ConstructionArgs::Nodes(vec![]), - ..Default::default() - }, - ), - ( - NodeId(1), - ProtoNode { - identifier: "id".into(), - input: ProtoNodeInput::Node(NodeId(11)), - construction_args: ConstructionArgs::Nodes(vec![]), - ..Default::default() - }, - ), - ( - NodeId(10), - ProtoNode { - identifier: "cons".into(), - input: ProtoNodeInput::ManualComposition(concrete!(u32)), - construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]), - ..Default::default() - }, - ), - ( - NodeId(11), - ProtoNode { - identifier: "add".into(), - input: ProtoNodeInput::Node(NodeId(10)), - construction_args: ConstructionArgs::Nodes(vec![]), - ..Default::default() - }, - ), - ( - NodeId(14), - ProtoNode { - identifier: "value".into(), - input: ProtoNodeInput::None, - construction_args: ConstructionArgs::Value(value::TaggedValue::U32(2).into()), - ..Default::default() - }, - ), - ] - .into_iter() - .collect(), - } - } +// fn test_network() -> ProtoNetwork { +// ProtoNetwork { +// inputs: vec![NodeId(10)], +// output: NodeId(1), +// nodes: [ +// ( +// NodeId(7), +// ProtoNode { +// identifier: "id".into(), +// input: ProtoNodeInput::Node(NodeId(11)), +// construction_args: ConstructionArgs::Nodes(vec![]), +// ..Default::default() +// }, +// ), +// ( +// NodeId(1), +// ProtoNode { +// identifier: "id".into(), +// input: ProtoNodeInput::Node(NodeId(11)), +// construction_args: ConstructionArgs::Nodes(vec![]), +// ..Default::default() +// }, +// ), +// ( +// NodeId(10), +// ProtoNode { +// identifier: "cons".into(), +// input: ProtoNodeInput::ManualComposition(concrete!(u32)), +// construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]), +// ..Default::default() +// }, +// ), +// ( +// NodeId(11), +// ProtoNode { +// identifier: "add".into(), +// input: ProtoNodeInput::Node(NodeId(10)), +// construction_args: ConstructionArgs::Nodes(vec![]), +// ..Default::default() +// }, +// ), +// ( +// NodeId(14), +// ProtoNode { +// identifier: "value".into(), +// input: ProtoNodeInput::None, +// construction_args: ConstructionArgs::Value(value::TaggedValue::U32(2).into()), +// ..Default::default() +// }, +// ), +// ] +// .into_iter() +// .collect(), +// } +// } - fn test_network_with_cycles() -> ProtoNetwork { - ProtoNetwork { - inputs: vec![NodeId(1)], - output: NodeId(1), - nodes: [ - ( - NodeId(1), - ProtoNode { - identifier: "id".into(), - input: ProtoNodeInput::Node(NodeId(2)), - construction_args: ConstructionArgs::Nodes(vec![]), - ..Default::default() - }, - ), - ( - NodeId(2), - ProtoNode { - identifier: "id".into(), - input: ProtoNodeInput::Node(NodeId(1)), - construction_args: ConstructionArgs::Nodes(vec![]), - ..Default::default() - }, - ), - ] - .into_iter() - .collect(), - } - } -} +// fn test_network_with_cycles() -> ProtoNetwork { +// ProtoNetwork { +// inputs: vec![NodeId(1)], +// output: NodeId(1), +// nodes: [ +// ( +// NodeId(1), +// ProtoNode { +// identifier: "id".into(), +// input: ProtoNodeInput::Node(NodeId(2)), +// construction_args: ConstructionArgs::Nodes(vec![]), +// ..Default::default() +// }, +// ), +// ( +// NodeId(2), +// ProtoNode { +// identifier: "id".into(), +// input: ProtoNodeInput::Node(NodeId(1)), +// construction_args: ConstructionArgs::Nodes(vec![]), +// ..Default::default() +// }, +// ), +// ] +// .into_iter() +// .collect(), +// } +// } +// } diff --git a/node-graph/graph-craft/src/util.rs b/node-graph/graph-craft/src/util.rs index eddeec842c..053ff1db48 100644 --- a/node-graph/graph-craft/src/util.rs +++ b/node-graph/graph-craft/src/util.rs @@ -1,6 +1,5 @@ use crate::document::NodeNetwork; use crate::graphene_compiler::Compiler; -use crate::proto::ProtoNetwork; pub fn load_network(document_string: &str) -> NodeNetwork { let document: serde_json::Value = serde_json::from_str(document_string).expect("Failed to parse document"); @@ -8,11 +7,6 @@ pub fn load_network(document_string: &str) -> NodeNetwork { serde_json::from_str::(&document).expect("Failed to parse document") } -pub fn compile(network: NodeNetwork) -> ProtoNetwork { - let compiler = Compiler {}; - compiler.compile_single(network).unwrap() -} - pub fn load_from_name(name: &str) -> NodeNetwork { let content = std::fs::read(format!("../../demo-artwork/{name}.graphite")).expect("failed to read file"); let content = std::str::from_utf8(&content).unwrap(); diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index af535b7363..fae70c5777 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -3,7 +3,7 @@ use fern::colors::{Color, ColoredLevelConfig}; use futures::executor::block_on; use graph_craft::document::*; use graph_craft::graphene_compiler::{Compiler, Executor}; -use graph_craft::proto::ProtoNetwork; +use graph_craft::proto::{ProtoNetwork, ProtoNode}; use graph_craft::util::load_network; use graph_craft::wasm_application_io::EditorPreferences; use graphene_core::text::FontCache; @@ -180,17 +180,17 @@ fn fix_nodes(network: &mut NodeNetwork) { } } } -fn compile_graph(document_string: String, editor_api: Arc) -> Result> { +fn compile_graph(document_string: String, editor_api: Arc) -> Result, Box> { let mut network = load_network(&document_string); fix_nodes(&mut network); let substitutions = preprocessor::generate_node_substitutions(); preprocessor::expand_network(&mut network, &substitutions); - let wrapped_network = wrap_network_in_scope(network.clone(), editor_api); + let mut wrapped_network = wrap_network_in_scope(network.clone(), editor_api); let compiler = Compiler {}; - compiler.compile_single(wrapped_network).map_err(|x| x.into()) + wrapped_network.flatten().map(|result|result.0).map_err(|x| x.into()) } fn create_executor(proto_network: ProtoNetwork) -> Result> { diff --git a/node-graph/interpreted-executor/benches/benchmark_util.rs b/node-graph/interpreted-executor/benches/benchmark_util.rs index f35d5c4daa..bc996e822a 100644 --- a/node-graph/interpreted-executor/benches/benchmark_util.rs +++ b/node-graph/interpreted-executor/benches/benchmark_util.rs @@ -2,13 +2,13 @@ use criterion::BenchmarkGroup; use criterion::measurement::Measurement; use futures::executor::block_on; use graph_craft::proto::ProtoNetwork; -use graph_craft::util::{DEMO_ART, compile, load_from_name}; +use graph_craft::util::{DEMO_ART, load_from_name}; use interpreted_executor::dynamic_executor::DynamicExecutor; pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) { - let network = load_from_name(name); - let proto_network = compile(network); - let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap(); + let mut network = load_from_name(name); + let proto_network = network.flatten().unwrap(); + let executor = block_on(DynamicExecutor::new(proto_network.0)).unwrap(); (executor, proto_network) } diff --git a/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs b/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs index fb0cf34094..2349c9693e 100644 --- a/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs +++ b/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs @@ -7,8 +7,8 @@ use graphene_std::transform::Footprint; use interpreted_executor::dynamic_executor::DynamicExecutor; fn update_executor(name: &str, c: &mut BenchmarkGroup) { - let network = load_from_name(name); - let proto_network = compile(network); + let mut network = load_from_name(name); + let proto_network = network.flatten().unwrap().0; let empty = ProtoNetwork::default(); let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); @@ -30,8 +30,8 @@ fn update_executor_demo(c: &mut Criterion) { } fn run_once(name: &str, c: &mut BenchmarkGroup) { - let network = load_from_name(name); - let proto_network = compile(network); + let mut network = load_from_name(name); + let proto_network = network.flatten().unwrap().0; let executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap(); let footprint = Footprint::default(); diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index cca13cb3a8..2b0dae6ef0 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -1,11 +1,15 @@ -use crate::node_registry; +use crate::node_registry::{MONITOR_NODES, NODE_REGISTRY}; use dyn_any::StaticType; -use graph_craft::Type; -use graph_craft::document::NodeId; +use glam::DAffine2; use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode}; -use graph_craft::graphene_compiler::Executor; -use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext}; +use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, downcast_node}; use graph_craft::proto::{GraphErrorType, GraphErrors}; +use graph_craft::{Type, concrete}; +use graphene_std::application_io::{ExportFormat, RenderConfig, TimingInformation}; +use graphene_std::memo::{IntrospectMode, MonitorNode}; +use graphene_std::transform::Footprint; +use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; +use graphene_std::{NodeIOTypes, OwnedContextImpl}; use std::collections::{HashMap, HashSet}; use std::error::Error; use std::sync::Arc; @@ -13,118 +17,116 @@ use std::sync::Arc; /// An executor of a node graph that does not require an online compilation server, and instead uses `Box`. #[derive(Clone)] pub struct DynamicExecutor { - output: NodeId, + output: Option, /// Stores all of the dynamic node structs. tree: BorrowTree, /// Stores the types of the proto nodes. typing_context: TypingContext, - // This allows us to keep the nodes around for one more frame which is used for introspection - orphaned_nodes: HashSet, + // TODO: Add lifetime for removed nodes so that if a SNI changes, then changes back to its previous SNI, the node does + // not have to be reinserted + // lifetime: HashSet<(SNI, usize)>, } impl Default for DynamicExecutor { fn default() -> Self { Self { - output: Default::default(), + output: None, tree: Default::default(), - typing_context: TypingContext::new(&node_registry::NODE_REGISTRY), - orphaned_nodes: HashSet::new(), + typing_context: TypingContext::new(&NODE_REGISTRY, &MONITOR_NODES), } } } -#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct NodeTypes { - pub inputs: Vec, - pub output: Type, -} - -#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct ResolvedDocumentNodeTypes { - pub types: HashMap, NodeTypes>, -} - -type Path = Box<[NodeId]>; - -#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct ResolvedDocumentNodeTypesDelta { - pub add: Vec<(Path, NodeTypes)>, - pub remove: Vec, -} - impl DynamicExecutor { - pub async fn new(proto_network: ProtoNetwork) -> Result { - let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY); + pub async fn new(proto_network: Vec) -> Result { + let mut typing_context = TypingContext::default(); typing_context.update(&proto_network)?; - let output = proto_network.output; + let output = proto_network.get(0).map(|protonode| protonode.stable_node_id); let tree = BorrowTree::new(proto_network, &typing_context).await?; - Ok(Self { - tree, - output, - typing_context, - orphaned_nodes: HashSet::new(), - }) + Ok(Self { tree, output, typing_context }) } /// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible. #[cfg_attr(debug_assertions, inline(never))] - pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result { - self.output = proto_network.output; + pub async fn update(mut self, proto_network: Vec) -> Result<(Vec<(SNI, Vec)>, Vec<(SNI, usize)>), GraphErrors> { + self.output = proto_network.get(0).map(|protonode| protonode.stable_node_id); self.typing_context.update(&proto_network)?; - let (add, orphaned) = self.tree.update(proto_network, &self.typing_context).await?; - let old_to_remove = core::mem::replace(&mut self.orphaned_nodes, orphaned); - let mut remove = Vec::with_capacity(old_to_remove.len() - self.orphaned_nodes.len().min(old_to_remove.len())); - for node_id in old_to_remove { - if self.orphaned_nodes.contains(&node_id) { - let path = self.tree.free_node(node_id); - self.typing_context.remove_inference(node_id); - if let Some(path) = path { - remove.push(path); - } - } + // A protonode id can change while having the same document path, and the path can change while having the same stable node id. + // Either way, the mapping of paths to ids and ids to paths has to be kept in sync. + // The mapping of monitor node paths has to kept in sync as well. + let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?; + let mut remove = Vec::new(); + for sni in orphaned_proto_nodes { + let Some(types) = self.typing_context.type_of(sni) else { + log::error!("Could not get type for protonode {sni} when removing"); + continue; + }; + remove.push((sni, types.inputs.len())); + self.tree.free_node(&sni, types.inputs.len()); + self.typing_context.remove_inference(&sni); } - let add = self.document_node_types(add.into_iter()).collect(); - Ok(ResolvedDocumentNodeTypesDelta { add, remove }) + + let add_with_types = add + .into_iter() + .filter_map(|sni| { + let Some(types) = self.typing_context.type_of(sni) else { + log::debug!("Could not get type for added node: {sni}"); + return None; + }; + Some((sni, types.inputs.clone())) + }) + .collect(); + + Ok((add_with_types, remove)) } - /// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path. - pub fn introspect(&self, node_path: &[NodeId]) -> Result, IntrospectError> { - self.tree.introspect(node_path) + /// Intospect the value for that specific protonode input, returning for example the cached value for a monitor node. + pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result, IntrospectError> { + let node = self.get_monitor_node_container(protonode_input)?; + node.introspect(introspect_mode).ok_or(IntrospectError::IntrospectNotImplemented) + } + + pub fn set_introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) { + let Ok(node) = self.get_monitor_node_container(protonode_input) else { + log::error!("Could not get monitor node for input: {:?}", protonode_input); + return; + }; + node.set_introspect(introspect_mode); + } + + pub fn get_monitor_node_container(&self, protonode_input: CompiledProtonodeInput) -> Result { + // The SNI of the monitor nodes are the ids of the protonode + input index + let monitor_node_id = NodeId(protonode_input.0.0 + protonode_input.1 as u64 + 1); + let inserted_node = self.tree.nodes.get(&monitor_node_id).ok_or(IntrospectError::ProtoNodeNotFound(monitor_node_id))?; + Ok(inserted_node.clone()) } pub fn input_type(&self) -> Option { - self.typing_context.type_of(self.output).map(|node_io| node_io.call_argument.clone()) + self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.call_argument.clone())) } pub fn tree(&self) -> &BorrowTree { &self.tree } - pub fn output(&self) -> NodeId { + pub fn output(&self) -> Option { self.output } pub fn output_type(&self) -> Option { - self.typing_context.type_of(self.output).map(|node_io| node_io.return_value.clone()) + self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.return_value.clone())) } - pub fn document_node_types<'a>(&'a self, nodes: impl Iterator + 'a) -> impl Iterator + 'a { - nodes.flat_map(|id| self.tree.source_map().get(&id).map(|(_, b)| (id, b.clone()))) - // TODO: https://github.com/GraphiteEditor/Graphite/issues/1767 - // TODO: Non exposed inputs are not added to the inputs_source_map, so they are not included in the resolved_document_node_types. The type is still available in the typing_context. This only affects the UI-only "Import" node. - } -} - -impl Executor for &DynamicExecutor -where - I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe, -{ - fn execute(&self, input: I) -> LocalFuture<'_, Result>> { + pub fn execute(&self, input: I) -> LocalFuture<'_, Result>> + where + I: dyn_any::StaticType + 'static + Send + Sync + std::panic::UnwindSafe, + { Box::pin(async move { use futures::FutureExt; + let output_node = self.output.ok_or("Could not execute network before compilation")?; - let result = self.tree.eval_tagged_value(self.output, input); + let result = self.tree.eval_tagged_value(output_node, input); let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await; match wrapped_result { @@ -136,15 +138,98 @@ where } }) } + + // If node to evaluate is None then the most downstream node is used + // pub async fn evaluate_from_node(&self, editor_context: EditorContext, node_to_evaluate: Option) -> Result { + // let node_to_evaluate: NodeId = node_to_evaluate + // .or_else(|| self.output) + // .ok_or("Could not find output node when evaluating network. Has the network been compiled?")?; + // let input_type = self + // .typing_context + // .type_of(node_to_evaluate) + // .map(|node_io| node_io.call_argument.clone()) + // .ok_or("Could not get input type of network to execute".to_string())?; + // let result = match input_type { + // t if t == concrete!(EditorContext) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()), + // t if t == concrete!(()) => (&self).execute((), node_to_evaluate).await.map_err(|e| e.to_string()), + // t => Err(format!("Invalid input type {t:?}")), + // }; + // let result = match result { + // Ok(value) => value, + // Err(e) => return Err(e), + // }; + + // Ok(result) + // } } -pub struct InputMapping {} + +#[derive(Debug, Clone, Default)] +pub struct EditorContext { + // pub footprint: Option, + // pub downstream_transform: Option, + // pub real_time: Option, + // pub animation_time: Option, + // pub index: Option, + // pub editor_var_args: Option<(Vec, Vec>>)>, + + // TODO: Temporarily used to execute with RenderConfig as call argument, will be removed once these fields can be passed + // As a scope input to the reworked render node. This will allow the Editor Context to be used to evaluate any node + pub render_config: RenderConfig, +} + +unsafe impl StaticType for EditorContext { + type Static = EditorContext; +} + +// impl Default for EditorContext { +// fn default() -> Self { +// EditorContext { +// footprint: None, +// downstream_transform: None, +// real_time: None, +// animation_time: None, +// index: None, +// // editor_var_args: None, +// } +// } +// } + +// impl EditorContext { +// pub fn to_context(&self) -> graphene_std::Context { +// let mut context = OwnedContextImpl::default(); +// if let Some(footprint) = self.footprint { +// context.set_footprint(footprint); +// } +// if let Some(footprint) = self.footprint { +// context.set_footprint(footprint); +// } +// if let Some(downstream_transform) = self.downstream_transform { +// context.set_downstream_transform(downstream_transform); +// } +// if let Some(real_time) = self.real_time { +// context.set_real_time(real_time); +// } +// if let Some(animation_time) = self.animation_time { +// context.set_animation_time(animation_time); +// } +// if let Some(index) = self.index { +// context.set_index(index); +// } +// // if let Some(editor_var_args) = self.editor_var_args { +// // let (variable_names, values) +// // context.set_varargs((variable_names, values)) +// // } +// context.into_context() +// } +// } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum IntrospectError { PathNotFound(Vec), - ProtoNodeNotFound(NodeId), + ProtoNodeNotFound(SNI), NoData, RuntimeNotReady, + IntrospectNotImplemented, } impl std::fmt::Display for IntrospectError { @@ -154,6 +239,7 @@ impl std::fmt::Display for IntrospectError { IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {:?}", id), IntrospectError::NoData => write!(f, "No data found for this node"), IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"), + IntrospectError::IntrospectNotImplemented => write!(f, "Intospect not implemented"), } } } @@ -178,55 +264,41 @@ impl std::fmt::Display for IntrospectError { /// A store of the dynamically typed nodes and also the source map. #[derive(Default, Clone)] pub struct BorrowTree { - /// A hashmap of node IDs and dynamically typed nodes. - nodes: HashMap, - /// A hashmap from the document path to the proto node ID. - source_map: HashMap, + // A hashmap of node IDs and dynamically typed nodes, as well as the number of inserted monitor nodes + nodes: HashMap, } impl BorrowTree { - pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result { + pub async fn new(proto_network: Vec, typing_context: &TypingContext) -> Result { let mut nodes = BorrowTree::default(); - for (id, node) in proto_network.nodes { - nodes.push_node(id, node, typing_context).await? + for node in proto_network { + nodes.push_node(node, typing_context).await? } Ok(nodes) } - /// Pushes new nodes into the tree and return orphaned nodes - pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec, HashSet), GraphErrors> { - let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect(); - let mut new_nodes: Vec<_> = Vec::new(); - // TODO: Problem: When an identity node is connected directly to an export the first input to identity node is not added to the proto network, while the second input is. This means the primary input does not have a type. - for (id, node) in proto_network.nodes { - if !self.nodes.contains_key(&id) { - new_nodes.push(node.original_location.path.clone().unwrap_or_default().into()); - self.push_node(id, node, typing_context).await?; - } else if self.update_source_map(id, typing_context, &node) { - new_nodes.push(node.original_location.path.clone().unwrap_or_default().into()); + /// Pushes new nodes into the tree and returns a vec of document nodes that had their types changed, and a vec of all nodes that were removed (including auto inserted value nodes) + pub async fn update(&mut self, proto_network: Vec, typing_context: &TypingContext) -> Result<(Vec, HashSet), GraphErrors> { + let mut old_nodes = self.nodes.keys().copied().into_iter().collect::>(); + // List of all document node paths that need to be updated, which occurs if their path changes or type changes + let mut nodes_with_new_type = Vec::new(); + for node in proto_network { + let sni = node.stable_node_id; + old_nodes.remove(&sni); + let sni = node.stable_node_id; + if !self.nodes.contains_key(&sni) { + if node.original_location.send_types_to_editor { + nodes_with_new_type.push(sni) + } + self.push_node(node, typing_context); } - old_nodes.remove(&id); } - Ok((new_nodes, old_nodes)) + + Ok((nodes_with_new_type, old_nodes)) } - fn node_deps(&self, nodes: &[NodeId]) -> Vec { - nodes.iter().map(|node| self.nodes.get(node).unwrap().0.clone()).collect() - } - - fn store_node(&mut self, node: SharedNodeContainer, id: NodeId, path: Path) { - self.nodes.insert(id, (node, path)); - } - - /// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path. - pub fn introspect(&self, node_path: &[NodeId]) -> Result, IntrospectError> { - let (id, _) = self.source_map.get(node_path).ok_or_else(|| IntrospectError::PathNotFound(node_path.to_vec()))?; - let (node, _path) = self.nodes.get(id).ok_or(IntrospectError::ProtoNodeNotFound(*id))?; - node.serialize().ok_or(IntrospectError::NoData) - } - - pub fn get(&self, id: NodeId) -> Option { - self.nodes.get(&id).map(|(node, _)| node.clone()) + fn node_deps(&self, nodes: &[SNI]) -> Vec { + nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect() } /// Evaluate the output node of the [`BorrowTree`]. @@ -235,18 +307,18 @@ impl BorrowTree { I: StaticType + 'i + Send + Sync, O: StaticType + 'i, { - let (node, _path) = self.nodes.get(&id).cloned()?; + let node = self.nodes.get(&id).cloned()?; let output = node.eval(Box::new(input)); dyn_any::downcast::(output.await).ok().map(|o| *o) } /// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value. /// This ensures that no borrowed data can escape the node graph. - pub async fn eval_tagged_value(&self, id: NodeId, input: I) -> Result + pub async fn eval_tagged_value(&self, id: SNI, input: I) -> Result where I: StaticType + 'static + Send + Sync, { - let (node, _path) = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?; - let output = node.eval(Box::new(input)); + let inserted_node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?; + let output = inserted_node.eval(Box::new(input)); TaggedValue::try_from_any(output.await) } @@ -305,58 +377,12 @@ impl BorrowTree { /// - Removes the node from `nodes` HashMap. /// - If the node is the primary node for its path in the `source_map`, it's also removed from there. /// - Returns `None` if the node is not found in the `nodes` HashMap. - pub fn free_node(&mut self, id: NodeId) -> Option { - let (_, path) = self.nodes.remove(&id)?; - if self.source_map.get(&path)?.0 == id { - self.source_map.remove(&path); - return Some(path); + pub fn free_node(&mut self, id: &SNI, inputs: usize) { + self.nodes.remove(&id); + // Also remove all corresponding monitor nodes + for monitor_index in 1..=inputs { + self.nodes.remove(&NodeId(id.0 + monitor_index as u64)); } - None - } - - /// Updates the source map for a given node in the [`BorrowTree`]. - /// - /// This method updates or inserts an entry in the `source_map` HashMap for the specified node, - /// using type information from the provided [`TypingContext`] and [`ProtoNode`]. - /// - /// # Arguments - /// - /// * `self` - Mutable reference to the [`BorrowTree`]. - /// * `id` - The `NodeId` of the node to update in the source map. - /// * `typing_context` - A reference to the [`TypingContext`] containing type information. - /// * `proto_node` - A reference to the [`ProtoNode`] containing original location information. - /// - /// # Returns - /// - /// `bool` - `true` if a new entry was inserted, `false` if an existing entry was updated. - /// - /// # Notes - /// - /// - Updates or inserts an entry in the `source_map` HashMap. - /// - Uses the `ProtoNode`'s original location path as the key for the source map. - /// - Collects input types from both the main input and parameters. - /// - Returns `false` and logs a warning if the node's type information is not found in the typing context. - fn update_source_map(&mut self, id: NodeId, typing_context: &TypingContext, proto_node: &ProtoNode) -> bool { - let Some(node_io) = typing_context.type_of(id) else { - log::warn!("did not find type"); - return false; - }; - let inputs = [&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned().collect(); - - let node_path = &proto_node.original_location.path.as_ref().unwrap_or(const { &vec![] }); - - let entry = self.source_map.entry(node_path.to_vec().into()).or_default(); - - let update = ( - id, - NodeTypes { - inputs, - output: node_io.return_value.clone(), - }, - ); - let modified = *entry != update; - *entry = update; - modified } /// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`. @@ -374,53 +400,58 @@ impl BorrowTree { /// - `Nodes`: Constructs a node using other nodes as dependencies. /// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments. /// - Returns an error if no constructor is found for the given node ID. - async fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> { - self.update_source_map(id, typing_context, &proto_node); - let path = proto_node.original_location.path.clone().unwrap_or_default(); - - match &proto_node.construction_args { + async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> { + let sni = proto_node.stable_node_id; + // Move the value into the upcast node instead of cloning it + match proto_node.construction_args { ConstructionArgs::Value(value) => { - let node = if let TaggedValue::EditorApi(api) = &**value { - let editor_api = UpcastAsRefNode::new(api.clone()); - let node = Box::new(editor_api) as TypeErasedBox<'_>; - NodeContainer::new(node) - } else { - let upcasted = UpcastNode::new(value.to_owned()); - let node = Box::new(upcasted) as TypeErasedBox<'_>; - NodeContainer::new(node) - }; - self.store_node(node, id, path.into()); + // The constructor for nodes with value construction args (value nodes) is not called. + // It is not necessary to clone the Arc for the wasm editor api, since the value node is deduplicated and only called once. + // It is cloned whenever it is evaluated + let upcasted = UpcastNode::new(value); + let node = Box::new(upcasted) as TypeErasedBox<'_>; + self.nodes.insert(sni, NodeContainer::new(node)); } ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), - ConstructionArgs::Nodes(ids) => { - let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect(); - let construction_nodes = self.node_deps(&ids); - let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?; - let node = constructor(construction_nodes).await; + ConstructionArgs::Nodes(ref node_construction_args) => { + let construction_nodes = self.node_deps(&node_construction_args.inputs); + + let types = typing_context.type_of(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?; + let monitor_nodes = construction_nodes + .into_iter() + .enumerate() + .map(|(input_index, construction_node)| { + let input_type = types.inputs.get(input_index).unwrap(); //.ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?; + let monitor_constructor = typing_context.monitor_constructor(input_type).unwrap(); // .ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?; + let monitor = monitor_constructor(construction_node); + let monitor_node_container = NodeContainer::new(monitor); + self.nodes.insert(NodeId(sni.0 + input_index as u64 + 1), monitor_node_container.clone()); + monitor_node_container + }) + .collect(); + + let constructor = typing_context.constructor(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?; + let node = constructor(monitor_nodes).await; let node = NodeContainer::new(node); - self.store_node(node, id, path.into()); + self.nodes.insert(sni, node); } }; Ok(()) } - - /// Returns the source map of the borrow tree - pub fn source_map(&self) -> &HashMap { - &self.source_map - } } #[cfg(test)] mod test { use super::*; use graph_craft::document::value::TaggedValue; + use graphene_std::uuid::NodeId; #[test] fn push_node_sync() { let mut tree = BorrowTree::default(); - let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![]); + let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![], NodeId(0)); let context = TypingContext::default(); - let future = tree.push_node(NodeId(0), val_1_protonode, &context); + let future = tree.push_node(val_1_protonode, &context); futures::executor::block_on(future).unwrap(); let _node = tree.get(NodeId(0)).unwrap(); let result = futures::executor::block_on(tree.eval(NodeId(0), ())); diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 5c05ef62ba..0265e3ad6a 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -43,8 +43,8 @@ mod tests { use graph_craft::graphene_compiler::Compiler; let compiler = Compiler {}; - let protograph = compiler.compile_single(network).expect("Graph should be generated"); + let protonetwork = network.flatten().map(|result| result.0).expect("Graph should be generated"); - let _exec = block_on(DynamicExecutor::new(protograph)).map(|_e| panic!("The network should not type check ")).unwrap_err(); + let _exec = block_on(DynamicExecutor::new(protonetwork)).map(|_e| panic!("The network should not type check ")).unwrap_err(); } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index ddd71abdbf..45e8b524ca 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,7 +1,7 @@ use dyn_any::StaticType; use glam::{DVec2, IVec2, UVec2}; use graph_craft::document::value::RenderOutput; -use graph_craft::proto::{NodeConstructor, TypeErasedBox}; +use graph_craft::proto::{MonitorConstructor, NodeConstructor, TypeErasedBox}; use graphene_core::raster::color::Color; use graphene_core::raster::*; use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; @@ -18,7 +18,7 @@ use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode}; use graphene_std::application_io::{ImageTexture, SurfaceFrame}; #[cfg(feature = "gpu")] use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle}; -use node_registry_macros::{async_node, convert_node, into_node}; +use node_registry_macros::{async_node, convert_node, into_node, monitor_node}; use once_cell::sync::Lazy; use std::collections::HashMap; #[cfg(feature = "gpu")] @@ -192,6 +192,52 @@ fn node_registry() -> HashMap>> = Lazy::new(|| node_registry()); +fn monitor_nodes() -> HashMap { + let nodes: Vec<(Type, MonitorConstructor)> = vec![ + monitor_node!(ImageTexture), + monitor_node!(VectorDataTable), + monitor_node!(GraphicGroupTable), + monitor_node!(GraphicElement), + monitor_node!(Artboard), + monitor_node!(RasterDataTable), + monitor_node!(RasterDataTable), + monitor_node!(graphene_core::instances::Instances), + monitor_node!(String), + monitor_node!(IVec2), + monitor_node!(DVec2), + monitor_node!(bool), + monitor_node!(f64), + monitor_node!(u32), + monitor_node!(u64), + monitor_node!(()), + monitor_node!(Vec), + monitor_node!(BlendMode), + monitor_node!(graphene_std::transform::ReferencePoint), + monitor_node!(graphene_path_bool::BooleanOperation), + monitor_node!(Option), + monitor_node!(graphene_core::vector::style::Fill), + monitor_node!(graphene_core::vector::style::StrokeCap), + monitor_node!(graphene_core::vector::style::StrokeJoin), + monitor_node!(graphene_core::vector::style::PaintOrder), + monitor_node!(graphene_core::vector::style::StrokeAlign), + monitor_node!(graphene_core::vector::style::Stroke), + monitor_node!(graphene_core::vector::style::Gradient), + monitor_node!(graphene_core::vector::style::GradientStops), + monitor_node!(Vec), + monitor_node!(Color), + monitor_node!(Box), + monitor_node!(graphene_std::vector::misc::CentroidType), + monitor_node!(graphene_std::vector::misc::PointSpacingType), + ]; + let mut monitor_nodes = HashMap::new(); + for (monitor_type, constructor) in nodes { + monitor_nodes.insert(monitor_type, constructor); + } + monitor_nodes +} + +pub static MONITOR_NODES: Lazy> = Lazy::new(|| monitor_nodes()); + mod node_registry_macros { macro_rules! async_node { // TODO: we currently need to annotate the type here because the compiler would otherwise (correctly) @@ -207,7 +253,7 @@ mod node_registry_macros { |mut args| { Box::pin(async move { args.reverse(); - let node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*); + let node = <$path>::new($(graphene_std::registry::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node); Box::new(any) as TypeErasedBox }) @@ -285,7 +331,18 @@ mod node_registry_macros { }; } + macro_rules! monitor_node { + ($type:ty) => { + (concrete!($type), |arg| { + let node = >::new(graphene_std::registry::downcast_node::(arg)); + let any: DynAnyNode<_, _, _> = graphene_std::any::DynAnyNode::new(node); + Box::new(any) as TypeErasedBox + }) + }; + } + pub(crate) use async_node; pub(crate) use convert_node; pub(crate) use into_node; + pub(crate) use monitor_node; } diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index e0f52dae20..dd496d1481 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -8,22 +8,13 @@ use graphene_std::Context; use graphene_std::uuid::NodeId; use std::sync::Arc; -// TODO: this is copy pasta from the editor (and does get out of sync) pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc) -> NodeNetwork { - network.generate_node_paths(&[]); - let inner_network = DocumentNode { implementation: DocumentNodeImplementation::Network(network), inputs: vec![], ..Default::default() }; - // TODO: Replace with "Output" definition? - // let render_node = resolve_document_node_type("Output") - // .expect("Output node type not found") - // .node_template_input_override(vec![Some(NodeInput::node(NodeId(1), 0)), Some(NodeInput::node(NodeId(0), 1))]) - // .document_node; - let render_node = DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)], implementation: DocumentNodeImplementation::Network(NodeNetwork { @@ -64,20 +55,12 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc