From cf0a32b9b12d5e5089ac22b83bed32c29b1a6e19 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 10 Jul 2025 01:47:40 -0700 Subject: [PATCH] Context nullification, cached monitor nodes --- editor/src/dispatcher.rs | 72 +- .../export_dialog_message_handler.rs | 2 +- .../new_document_dialog_message_handler.rs | 11 +- .../src/messages/frontend/frontend_message.rs | 2 +- .../input_preprocessor_message_handler.rs | 3 +- editor/src/messages/message.rs | 27 +- .../portfolio/document/document_message.rs | 3 +- .../document/document_message_handler.rs | 47 +- .../graph_operation_message.rs | 2 +- .../graph_operation_message_handler.rs | 5 +- .../graph_operation/transform_utils.rs | 5 +- .../document/graph_operation/utility_types.rs | 5 +- .../navigation/navigation_message_handler.rs | 2 +- .../node_graph/document_node_definitions.rs | 2 +- .../document/node_graph/node_graph_message.rs | 9 +- .../node_graph/node_graph_message_handler.rs | 83 +-- .../document/node_graph/node_properties.rs | 4 +- .../document/node_graph/utility_types.rs | 7 +- .../document/properties_panel/mod.rs | 4 +- .../properties_panel_message_handler.rs | 1 - .../document/utility_types/clipboards.rs | 3 +- .../utility_types/document_metadata.rs | 2 +- .../utility_types/network_interface.rs | 172 ++--- .../portfolio/document/utility_types/wires.rs | 23 +- .../messages/portfolio/document_migration.rs | 13 +- .../messages/portfolio/portfolio_message.rs | 39 +- .../portfolio/portfolio_message_handler.rs | 621 ++++++++---------- .../spreadsheet/spreadsheet_message.rs | 5 +- .../spreadsheet_message_handler.rs | 36 +- .../src/messages/portfolio/utility_types.rs | 2 +- .../preferences_message_handler.rs | 6 +- .../shape_gizmos/number_of_points_dial.rs | 5 +- .../shape_gizmos/point_radius_handle.rs | 6 +- .../graph_modification_utils.rs | 7 +- .../shapes/ellipse_shape.rs | 4 +- .../common_functionality/shapes/line_shape.rs | 4 +- .../shapes/polygon_shape.rs | 3 +- .../shapes/rectangle_shape.rs | 4 +- .../shapes/shape_utility.rs | 3 +- .../common_functionality/shapes/star_shape.rs | 4 +- .../tool/tool_messages/artboard_tool.rs | 2 +- .../messages/tool/tool_messages/brush_tool.rs | 5 +- .../tool/tool_messages/freehand_tool.rs | 3 +- .../messages/tool/tool_messages/path_tool.rs | 4 +- .../messages/tool/tool_messages/pen_tool.rs | 5 +- .../tool/tool_messages/select_tool.rs | 2 +- .../messages/tool/tool_messages/shape_tool.rs | 10 +- .../tool/tool_messages/spline_tool.rs | 5 +- .../messages/tool/tool_messages/text_tool.rs | 11 +- editor/src/node_graph_executor.rs | 192 +++--- editor/src/node_graph_executor/runtime.rs | 157 ++--- frontend/src/components/views/Graph.svelte | 13 +- frontend/src/messages.ts | 5 +- frontend/src/state-providers/node-graph.ts | 9 +- frontend/wasm/src/editor_api.rs | 2 +- libraries/bezier-rs/src/subpath/solvers.rs | 15 + libraries/dyn-any/src/lib.rs | 15 + node-graph/gapplication-io/src/lib.rs | 65 +- node-graph/gcore/src/animation.rs | 6 +- node-graph/gcore/src/context.rs | 394 ++++++++--- node-graph/gcore/src/lib.rs | 2 +- node-graph/gcore/src/memo.rs | 98 ++- node-graph/gcore/src/registry.rs | 15 +- node-graph/gcore/src/structural.rs | 2 +- node-graph/gcore/src/uuid.rs | 4 +- .../gcore/src/vector/algorithms/instance.rs | 2 +- node-graph/graph-craft/src/document.rs | 420 +++++++----- node-graph/graph-craft/src/document/value.rs | 116 +++- node-graph/graph-craft/src/proto.rs | 211 +++--- .../graph-craft/src/wasm_application_io.rs | 13 +- node-graph/graphene-cli/src/main.rs | 25 +- node-graph/gstd/src/any.rs | 117 ++++ node-graph/gstd/src/text.rs | 7 +- node-graph/gstd/src/wasm_application_io.rs | 130 ++-- node-graph/gsvg-renderer/src/renderer.rs | 24 + .../benches/benchmark_util.rs | 2 +- .../src/dynamic_executor.rs | 360 +++++----- .../interpreted-executor/src/node_registry.rs | 104 +-- node-graph/interpreted-executor/src/util.rs | 26 +- node-graph/node-macro/src/codegen.rs | 22 +- node-graph/node-macro/src/parsing.rs | 27 + node-graph/wgpu-executor/src/lib.rs | 14 +- 82 files changed, 2235 insertions(+), 1684 deletions(-) diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 0349d5ad5..95651b551 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -5,8 +5,10 @@ use crate::messages::prelude::*; #[derive(Debug, Default)] pub struct Dispatcher { - buffered_queue: Vec, - queueing_messages: bool, + evaluation_queue: Vec, + introspection_queue: Vec, + queueing_evaluation_messages: bool, + queueing_introspection_messages: bool, message_queues: Vec>, pub responses: Vec, pub message_handlers: DispatcherMessageHandlers, @@ -41,6 +43,9 @@ impl DispatcherMessageHandlers { /// The last occurrence of the message in the message queue is sufficient to ensure correct behavior. /// In addition, these messages do not change any state in the backend (aside from caches). const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ + MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::CompileActiveDocument), + MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::EvaluateActiveDocument), + MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::IntrospectActiveDocument), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( PropertiesPanelMessageDiscriminant::Refresh, ))), @@ -91,13 +96,6 @@ 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 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; - } - } // If we are not maintaining the buffer, simply add to the current queue Self::schedule_execution(&mut self.message_queues, process_after_all_current, [message]); @@ -117,7 +115,21 @@ impl Dispatcher { continue; } } + // Add all messages to the queue if queuing messages (except from the end queue message) + if !matches!(message, Message::EndEvaluationQueue) { + if self.queueing_evaluation_messages { + self.evaluation_queue.push(message); + return; + } + } + // Add all messages to the queue if queuing messages (except from the end queue message) + if !matches!(message, Message::EndIntrospectionQueue) { + if self.queueing_introspection_messages { + self.introspection_queue.push(message); + return; + } + } // Print the message at a verbosity level of `info` self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity); @@ -126,22 +138,40 @@ 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::StartEvaluationQueue => { + self.queueing_evaluation_messages = true; } - Message::EndQueue => { - self.queueing_messages = false; + Message::EndEvaluationQueue => { + self.queueing_evaluation_messages = false; } - Message::ProcessQueue((render_output_metadata, introspected_inputs)) => { - let message = PortfolioMessage::ProcessEvaluationResponse { + Message::ProcessEvaluationQueue(render_output_metadata) => { + let update_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]); + } + .into(); + // Update the state with the render output and introspected inputs + Self::schedule_execution(&mut self.message_queues, true, [update_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)); + // Schedule all queued messages to be run, which use the introspected inputs (in the order they were added) + Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.evaluation_queue)); + } + Message::StartIntrospectionQueue => { + self.queueing_introspection_messages = true; + } + Message::EndIntrospectionQueue => { + self.queueing_introspection_messages = false; + } + Message::ProcessIntrospectionQueue(introspected_inputs) => { + let update_message = PortfolioMessage::ProcessIntrospectionResponse { introspected_inputs }.into(); + // Update the state with the render output and introspected inputs + Self::schedule_execution(&mut self.message_queues, true, [update_message]); + + // Schedule all queued messages to be run, which use the introspected inputs (in the order they were added) + Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.introspection_queue)); + + let clear_message = PortfolioMessage::ClearIntrospectedData.into(); + // Clear the introspected inputs since they are no longer required, and will cause a memory leak if not removed + Self::schedule_execution(&mut self.message_queues, true, [clear_message]); } Message::NoOp => {} Message::Init => { 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 91b80de3e..9fe673308 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::ActiveDocumentExport { + ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::ExportActiveDocument { 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 1162f2f10..49476d9e8 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 @@ -1,7 +1,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; use glam::{IVec2, UVec2}; -use graph_craft::document::NodeId; +use graphene_std::uuid::NodeId; /// A dialog to allow users to set some initial options about a new document. #[derive(Debug, Clone, Default, ExtractField)] @@ -24,17 +24,14 @@ impl MessageHandler for NewDocumentDialogMessageHa let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0; if create_artboard { - responses.add(Message::StartQueue); responses.add(GraphOperationMessage::NewArtboard { id: NodeId::new(), artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()), }); } - - // 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::StartQueue); - responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); + responses.add(Message::StartEvaluationQueue); + responses.add(DocumentMessage::ZoomCanvasToFitAll); + responses.add(Message::EndEvaluationQueue); responses.add(DocumentMessage::DeselectAllLayers); } } diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index c90f86122..addb7491c 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -7,7 +7,7 @@ use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, La use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate}; use crate::messages::prelude::*; use crate::messages::tool::utility_types::HintData; -use graph_craft::document::NodeId; +use graphene_std::uuid::NodeId; use graphene_std::raster::color::Color; use graphene_std::text::Font; 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 16d4c5c96..ec62a2d01 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -3,8 +3,7 @@ use crate::messages::input_mapper::utility_types::input_mouse::{MouseButton, Mou use crate::messages::input_mapper::utility_types::misc::FrameTimeInfo; use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::prelude::*; -use glam::DVec2; -use std::time::Duration; +use glam::{DAffine2, DVec2}; #[derive(ExtractField)] pub struct InputPreprocessorMessageContext { diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index c1cb9a4ba..16a421744 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -1,27 +1,24 @@ -use std::sync::Arc; - -use crate::messages::prelude::*; -use graphene_std::{IntrospectMode, uuid::CompiledProtonodeInput}; +use crate::{messages::prelude::*, node_graph_executor::IntrospectionResponse}; use graphite_proc_macros::*; #[impl_message] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub enum Message { NoOp, Init, Batched(Box<[Message]>), // Adds any subsequent messages to the queue - StartQueue, + StartEvaluationQueue, // 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)>, - ), - ), - + EndEvaluationQueue, + // Processes all messages that are queued to be run after evaluation, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete + #[serde(skip)] + ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata), + StartIntrospectionQueue, + EndIntrospectionQueue, + // Processes all messages that are queued to be run after introspection, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete + #[serde(skip)] + ProcessIntrospectionQueue(IntrospectionResponse), #[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 3cd6bdeaf..dcf16f207 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -7,11 +7,10 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, use crate::messages::portfolio::utility_types::PanelType; use crate::messages::prelude::*; use glam::DAffine2; -use graphene_std::uuid::CompiledProtonodeInput; +use graphene_std::vector::click_target::ClickTarget; 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::uuid::NodeId; use graphene_std::vector::style::ViewMode; diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index ffe5c6411..09ffd3b6a 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -3,7 +3,7 @@ use super::node_graph::utility_types::Transform; use super::overlays::utility_types::Pivot; use super::utility_types::error::EditorError; use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState}; -use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus}; +use super::utility_types::network_interface::{NodeNetworkInterface, TransactionStatus}; use super::utility_types::nodes::{CollapsedLayers, SelectedNodes}; use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid}; use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL}; @@ -13,10 +13,9 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext; use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options}; use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings}; -use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ}; -use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate}; +use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeTemplate}; use crate::messages::portfolio::document::utility_types::nodes::RawBuffer; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; @@ -24,11 +23,10 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::ToolType; -use crate::node_graph_executor::NodeGraphExecutor; use bezier_rs::Subpath; use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork}; +use graph_craft::document::{InputConnector, NodeInput, NodeNetwork, OldNodeNetwork}; use graphene_std::math::quad::Quad; use graphene_std::path_bool::{boolean_intersect, path_bool_lib}; use graphene_std::raster::BlendMode; @@ -37,18 +35,16 @@ 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)] -pub struct DocumentMessageContext<'a> { - pub document_id: DocumentId, +pub struct DocumentMessageData<'a> { pub ipp: &'a InputPreprocessorMessageHandler, pub persistent_data: &'a PersistentData, pub current_tool: &'a ToolType, pub preferences: &'a PreferencesMessageHandler, pub device_pixel_ratio: f64, - // pub introspected_inputs: &HashMap>, + // pub introspected_inputs: &HashMap>, // pub downcasted_inputs: &mut HashMap, } @@ -173,10 +169,9 @@ impl Default for DocumentMessageHandler { } #[message_handler_data] -impl MessageHandler> for DocumentMessageHandler { - fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque, context: DocumentMessageContext) { - let DocumentMessageContext { - document_id, +impl MessageHandler> for DocumentMessageHandler { + fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque, data: DocumentMessageData) { + let DocumentMessageData { ipp, persistent_data, current_tool, @@ -222,12 +217,10 @@ impl MessageHandler> for DocumentMes ); } DocumentMessage::PropertiesPanel(message) => { - let context = PropertiesPanelMessageContext { + let properties_panel_message_handler_data = super::properties_panel::PropertiesPanelMessageHandlerData { network_interface: &mut self.network_interface, selection_network_path: &self.selection_network_path, document_name: self.name.as_str(), - executor, - persistent_data, }; self.properties_panel_message_handler.process_message(message, responses, context); } @@ -239,7 +232,6 @@ impl MessageHandler> for DocumentMes network_interface: &mut self.network_interface, selection_network_path: &self.selection_network_path, breadcrumb_network_path: &self.breadcrumb_network_path, - document_id, collapsed: &mut self.collapsed, ipp, graph_view_overlay_open: self.graph_view_overlay_open, @@ -1432,7 +1424,7 @@ impl MessageHandler> for DocumentMes center: Key::Alt, duplicate: Key::Alt, })); - responses.add(PortfolioMessage::CompileActiveDocument); + responses.add(PortfolioMessage::EvaluateActiveDocument); } else { let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else { return; @@ -1492,7 +1484,7 @@ impl MessageHandler> for DocumentMes // Connect the current output data to the artboard's input data, and the artboard's output to the document output responses.add(NodeGraphMessage::InsertNodeBetween { node_id, - input_connector: network_interface::InputConnector::Export(0), + input_connector: InputConnector::Export(0), insert_node_input_index: 1, }); @@ -1914,13 +1906,14 @@ impl DocumentMessageHandler { let previous_network = std::mem::replace(&mut self.network_interface, network_interface); // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents + responses.add(PortfolioMessage::CompileActiveDocument); + responses.add(Message::StartEvaluationQueue); responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(NodeGraphMessage::SelectedNodesUpdated); - // 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(PortfolioMessage::CompileActiveDocument); - responses.add(Message::StartQueue); + responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::SendWires); + responses.add(Message::EndEvaluationQueue); Some(previous_network) } pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { @@ -1946,12 +1939,14 @@ impl DocumentMessageHandler { network_interface.set_document_to_viewport_transform(transform); let previous_network = std::mem::replace(&mut self.network_interface, network_interface); + responses.add(PortfolioMessage::CompileActiveDocument); + responses.add(Message::StartEvaluationQueue); // 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(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::SendWires); + responses.add(Message::EndEvaluationQueue); Some(previous_network) } @@ -2108,7 +2103,7 @@ impl DocumentMessageHandler { /// Loads all of the fonts in the document. pub fn load_layer_resources(&self, responses: &mut VecDeque) { let mut fonts = HashSet::new(); - for (_node_id, node, _) in self.document_network().recursive_nodes() { + for (_node_path, node) in self.document_network().recursive_nodes() { for input in &node.inputs { if let Some(TaggedValue::Font(font)) = input.as_value() { fonts.insert(font.clone()); @@ -2581,7 +2576,7 @@ impl DocumentMessageHandler { layout: Layout::WidgetLayout(document_bar_layout), layout_target: LayoutTarget::DocumentBar, }); - responses.add(NodeGraphMessage::ForceRunDocumentGraph); + responses.add(PortfolioMessage::EvaluateActiveDocument); } pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index 31ea83121..628ee0834 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -4,12 +4,12 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node use crate::messages::prelude::*; use bezier_rs::Subpath; use glam::{DAffine2, IVec2}; -use graph_craft::document::NodeId; use graphene_std::Artboard; use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::raster::BlendMode; use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::text::{Font, TypesettingConfig}; +use graphene_std::uuid::NodeId; use graphene_std::vector::PointId; use graphene_std::vector::VectorModificationType; use graphene_std::vector::style::{Fill, Stroke}; 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 a4a172489..71a02107d 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 @@ -2,12 +2,13 @@ use super::transform_utils; use super::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector}; +use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; use glam::{DAffine2, DVec2, IVec2}; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::{InputConnector, OutputConnector, NodeInput}; +use graphene_std::uuid::NodeId; use graphene_std::Color; use graphene_std::renderer::Quad; use graphene_std::renderer::convert_usvg_path::convert_usvg_path; diff --git a/editor/src/messages/portfolio/document/graph_operation/transform_utils.rs b/editor/src/messages/portfolio/document/graph_operation/transform_utils.rs index 2b3639516..f81304d12 100644 --- a/editor/src/messages/portfolio/document/graph_operation/transform_utils.rs +++ b/editor/src/messages/portfolio/document/graph_operation/transform_utils.rs @@ -1,8 +1,9 @@ -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface}; +use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use bezier_rs::Subpath; use glam::{DAffine2, DVec2}; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::{InputConnector, NodeInput}; +use graphene_std::uuid::NodeId; use graphene_std::vector::PointId; /// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`. 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 d4c201193..270e83f40 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -1,18 +1,19 @@ use super::transform_utils; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector}; +use crate::messages::portfolio::document::utility_types::network_interface::{self, NodeNetworkInterface}; use crate::messages::prelude::*; use bezier_rs::Subpath; use glam::{DAffine2, IVec2}; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::{InputConnector, NodeInput, OutputConnector}; use graphene_std::Artboard; use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::raster::BlendMode; use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::text::{Font, TypesettingConfig}; +use graphene_std::uuid::NodeId; use graphene_std::vector::style::{Fill, Stroke}; use graphene_std::vector::{PointId, VectorModificationType}; use graphene_std::vector::{VectorData, VectorDataTable}; diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index c1128f008..cbe2f9996 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -11,7 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node use crate::messages::prelude::*; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::{DAffine2, DVec2}; -use graph_craft::document::NodeId; +use graphene_std::uuid::NodeId; #[derive(ExtractField)] pub struct NavigationMessageContext<'a> { 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 5520238a2..d687c3a1b 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 @@ -10,7 +10,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::{ }; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::Message; -use crate::node_graph_executor::NodeGraphExecutor; use glam::DVec2; use graph_craft::ProtoNodeIdentifier; use graph_craft::concrete; @@ -23,6 +22,7 @@ use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::text::{Font, TypesettingConfig}; #[allow(unused_imports)] use graphene_std::transform::Footprint; +use graphene_std::uuid::NodeId; use graphene_std::vector::VectorDataTable; use graphene_std::*; use std::collections::{HashMap, HashSet, VecDeque}; 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 7f60d5f77..da2344d40 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 @@ -1,13 +1,12 @@ use super::utility_types::Direction; use crate::messages::input_mapper::utility_types::input_keyboard::Key; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, InputConnector, NodeTemplate, OutputConnector}; +use crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, NodeTemplate}; use crate::messages::prelude::*; use glam::IVec2; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; -use graph_craft::proto::GraphErrors; -use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta; +use graph_craft::document::{InputConnector, NodeInput, OutputConnector}; +use graphene_std::uuid::NodeId; #[impl_message(Message, DocumentMessage, NodeGraph)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -111,8 +110,6 @@ pub enum NodeGraphMessage { start_index: usize, end_index: usize, }, - RunDocumentGraph, - ForceRunDocumentGraph, SelectedNodesAdd { nodes: Vec, }, 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 ae6431d9a..ec6025fc8 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 @@ -9,9 +9,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; -use crate::messages::portfolio::document::utility_types::network_interface::{ - self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, -}; +use crate::messages::portfolio::document::utility_types::network_interface::{self, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, Previewing, TypeSource}; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::prelude::*; @@ -20,9 +18,10 @@ use crate::messages::tool::common_functionality::graph_modification_utils::get_c use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::{DAffine2, DVec2, IVec2}; -use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; +use graph_craft::document::{DocumentNodeImplementation, InputConnector, NodeInput, OutputConnector}; use graph_craft::proto::GraphErrors; use graphene_std::math::math_ext::QuadExt; +use graphene_std::uuid::NodeId; use graphene_std::*; use renderer::Quad; use std::cmp::Ordering; @@ -32,7 +31,6 @@ pub struct NodeGraphMessageContext<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], pub breadcrumb_network_path: &'a [NodeId], - pub document_id: DocumentId, pub collapsed: &'a mut CollapsedLayers, pub ipp: &'a InputPreprocessorMessageHandler, pub graph_view_overlay_open: bool, @@ -99,7 +97,6 @@ impl<'a> MessageHandler> for NodeG network_interface, selection_network_path, breadcrumb_network_path, - document_id, collapsed, ipp, graph_view_overlay_open, @@ -767,14 +764,8 @@ impl<'a> MessageHandler> for NodeG self.initial_disconnecting = false; self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path); - if let Some((output_type, source)) = clicked_output - .node_id() - .map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path)) - { - self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source); - } else { - self.wire_in_progress_type = FrontendGraphDataType::General; - } + let (output_type, source) = network_interface.output_type(&clicked_output, breadcrumb_network_path); + self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source); self.update_node_graph_hints(responses); return; @@ -922,7 +913,7 @@ impl<'a> MessageHandler> for NodeG false } }); - let vector_wire = build_vector_wire( + let (vector_wire, _) = build_vector_wire( wire_in_progress_from_connector, wire_in_progress_to_connector, from_connector_is_layer, @@ -936,6 +927,8 @@ impl<'a> MessageHandler> for NodeG data_type: self.wire_in_progress_type, thick: false, dashed: false, + center: None, + input_sni: None, }; responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) }); } @@ -1078,15 +1071,13 @@ impl<'a> MessageHandler> for NodeG }; // Get the compatible type from the output connector let compatible_type = output_connector.and_then(|output_connector| { - output_connector.node_id().and_then(|node_id| { - // Get the output types from the network interface - let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path); + // Get the output types from the network interface + let (output_type, type_source) = network_interface.output_type(&output_connector, selection_network_path); - match type_source { - TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None, - _ => Some(format!("type:{}", output_type.nested_type())), - } - }) + match type_source { + TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None, + _ => Some(format!("type:{}", output_type.nested_type())), + } }); let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. }; let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. }; @@ -1188,7 +1179,7 @@ impl<'a> MessageHandler> for NodeG { return None; } - let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?; + let (wire, is_stack, _) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?; wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack)) }) .collect::>(); @@ -1290,12 +1281,6 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SendGraph); responses.add(PortfolioMessage::CompileActiveDocument); } - PortfolioMessage::CompileActiveDocument => { - responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false }); - } - NodeGraphMessage::ForceRunDocumentGraph => { - responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: true }); - } NodeGraphMessage::SelectedNodesAdd { nodes } => { let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else { log::error!("Could not get selected nodes in NodeGraphMessage::SelectedNodesAdd"); @@ -1340,14 +1325,13 @@ impl<'a> MessageHandler> for NodeG let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else { return; }; - let document_bbox: [DVec2; 2] = ipp.document_bounds(); + let document_bbox = ipp.document_bounds(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse()); 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 { log::error!("Could not get bbox for node: {:?}", node_id); continue; }; - if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y { nodes.push(*node_id); } @@ -1393,6 +1377,9 @@ 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(PortfolioMessage::CompileActiveDocument); + responses.add(Message::StartEvaluationQueue); + responses.add(NodeGraphMessage::SendWires); + responses.add(Message::EndEvaluationQueue); } } NodeGraphMessage::SetInput { input_connector, input } => { @@ -1688,8 +1675,6 @@ impl<'a> MessageHandler> for NodeG ) .into_iter() .next(); - responses.add(NodeGraphMessage::UpdateVisibleNodes); - responses.add(NodeGraphMessage::SendWires); responses.add(FrontendMessage::UpdateImportsExports { imports, exports, @@ -2182,6 +2167,7 @@ impl NodeGraphMessageHandler { fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec { let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path).cloned() else { + log::error!("Could not collect outward wires in collect_nodes"); return Vec::new(); }; let mut can_be_layer_lookup = HashSet::new(); @@ -2232,7 +2218,7 @@ impl NodeGraphMessageHandler { let primary_input = inputs.next().flatten(); let exposed_inputs = inputs.flatten().collect(); - let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path); + let (output_type, type_source) = network_interface.output_type(&OutputConnector::node(node_id, 0), breadcrumb_network_path); let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source); let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default(); @@ -2253,7 +2239,7 @@ impl NodeGraphMessageHandler { if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) { continue; } - let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path); + let (output_type, type_source) = network_interface.output_type(&OutputConnector::node(node_id, 0), breadcrumb_network_path); let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source); let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else { @@ -2292,18 +2278,19 @@ impl NodeGraphMessageHandler { let locked = network_interface.is_locked(&node_id, breadcrumb_network_path); - let errors = self - .node_graph_errors - .iter() - .find(|error| error.node_path == node_id_path) - .map(|error| format!("{:?}", error.error.clone())) - .or_else(|| { - if self.node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) { - Some("Node graph type error within this node".to_string()) - } else { - None - } - }); + let errors = None; // TODO: Recursive traversal from export over all protonodes and match metadata with error + // self + // .node_graph_errors + // .iter() + // .find(|error| error.stable_node_id == node_id_path) + // .map(|error| format!("{:?}", error.error.clone())) + // .or_else(|| { + // if self.node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) { + // Some("Node graph type error within this node".to_string()) + // } else { + // None + // } + // }); nodes.push(FrontendNode { id: node_id, diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index c69574b9a..83a9d0989 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -3,14 +3,13 @@ use super::document_node_definitions::{NODE_OVERRIDES, NodePropertiesContext}; use super::utility_types::FrontendGraphDataType; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::prelude::*; use choice::enum_choice; use dyn_any::DynAny; use glam::{DAffine2, DVec2, IVec2, UVec2}; use graph_craft::Type; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; +use graph_craft::document::{DocumentNode, DocumentNodeImplementation, InputConnector, NodeInput}; use graphene_std::animation::RealTimeMode; use graphene_std::extract_xy::XY; use graphene_std::path_bool::BooleanOperation; @@ -22,6 +21,7 @@ use graphene_std::raster::{ use graphene_std::raster_types::{CPU, GPU, RasterDataTable}; use graphene_std::text::Font; use graphene_std::transform::{Footprint, ReferencePoint}; +use graphene_std::uuid::NodeId; use graphene_std::vector::VectorDataTable; use graphene_std::vector::misc::GridType; use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm}; diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index e752c9d7c..af5ecf681 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -1,6 +1,7 @@ -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector, TypeSource}; -use graph_craft::document::NodeId; +use crate::messages::portfolio::document::utility_types::network_interface::{TypeSource}; +use graph_craft::document::{InputConnector, OutputConnector}; use graph_craft::document::value::TaggedValue; +use graphene_std::uuid::NodeId; use graphene_std::Type; use std::borrow::Cow; @@ -72,7 +73,7 @@ pub struct FrontendGraphOutput { #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct FrontendNode { - pub id: graph_craft::document::NodeId, + pub id: NodeId, #[serde(rename = "isLayer")] pub is_layer: bool, #[serde(rename = "canBeLayer")] diff --git a/editor/src/messages/portfolio/document/properties_panel/mod.rs b/editor/src/messages/portfolio/document/properties_panel/mod.rs index cc8c29167..763d3d52c 100644 --- a/editor/src/messages/portfolio/document/properties_panel/mod.rs +++ b/editor/src/messages/portfolio/document/properties_panel/mod.rs @@ -1,7 +1,7 @@ mod properties_panel_message; -pub mod properties_panel_message_handler; +mod properties_panel_message_handler; #[doc(inline)] pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant}; #[doc(inline)] -pub use properties_panel_message_handler::PropertiesPanelMessageHandler; +pub use properties_panel_message_handler::{PropertiesPanelMessageHandler, PropertiesPanelMessageHandlerData}; 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 0d4b50909..adac91970 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 @@ -7,7 +7,6 @@ use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; 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], diff --git a/editor/src/messages/portfolio/document/utility_types/clipboards.rs b/editor/src/messages/portfolio/document/utility_types/clipboards.rs index 158372cd4..23dc08141 100644 --- a/editor/src/messages/portfolio/document/utility_types/clipboards.rs +++ b/editor/src/messages/portfolio/document/utility_types/clipboards.rs @@ -1,5 +1,6 @@ +use graphene_std::uuid::NodeId; + use super::network_interface::NodeTemplate; -use graph_craft::document::NodeId; #[repr(u8)] #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)] diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 0b6f65bc8..ca20f917c 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -4,9 +4,9 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Modify use crate::messages::portfolio::document::utility_types::network_interface::FlowType; use crate::messages::tool::common_functionality::graph_modification_utils; use glam::{DAffine2, DVec2}; -use graph_craft::document::NodeId; use graphene_std::math::quad::Quad; use graphene_std::transform::Footprint; +use graphene_std::uuid::NodeId; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::{PointId, VectorData}; use std::collections::{HashMap, HashSet}; 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 acaa7364f..f8d3f22a4 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -11,15 +11,13 @@ 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, InputConnector, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork, OutputConnector}; +use graph_craft::document::{DocumentNode, DocumentNodeImplementation, InputConnector, 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::uuid::{CompiledProtonodeInput, NodeId, SNI}; 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}; use std::collections::{HashMap, HashSet, VecDeque}; @@ -38,8 +36,9 @@ pub struct NodeNetworkInterface { #[serde(skip)] document_metadata: DocumentMetadata, /// All input/output types based on the compiled network. + /// TODO: Move to portfolio message handler #[serde(skip)] - pub resolved_types: ResolvedDocumentNodeTypes, + pub resolved_types: HashMap>, #[serde(skip)] transaction_status: TransactionStatus, #[serde(skip)] @@ -54,6 +53,7 @@ impl Clone for NodeNetworkInterface { document_metadata: Default::default(), resolved_types: Default::default(), transaction_status: TransactionStatus::Finished, + current_hash: 0, } } } @@ -490,7 +490,7 @@ impl NodeNetworkInterface { } /// Try and get the [`DocumentNodeDefinition`] for a node - pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> { + pub fn node_definition(&self, node_id: NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeDefinition> { let metadata = self.node_metadata(&node_id, network_path)?; resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) } @@ -512,13 +512,13 @@ impl NodeNetworkInterface { } } - pub fn downstream_caller_from_output(&self, output_connector: OutputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> { + 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) => { + OutputConnector::Node { node_id, output_index } => match self.implementation(node_id, network_path)? { + DocumentNodeImplementation::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) + 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!(), @@ -526,44 +526,44 @@ impl NodeNetworkInterface { 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) + 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> { + 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 } => { + NodeInput::Node { node_id, output_index, .. } => { match self.implementation(node_id, network_path)? { - DocumentNodeImplementation::Network(node_network) => { + DocumentNodeImplementation::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) + 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(), + DocumentNodeImplementation::ProtoNode(_) => 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(), + InputConnector::Node { node_id, .. } => self.transient_input_metadata(node_id, input_connector.input_index(), network_path)?.caller.as_ref(), + InputConnector::Export(export_index) => self.network_metadata(network_path)?.transient_metadata.callers.get(*export_index)?.as_ref(), }, - NodeInput::Network { import_index } => { + 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) + self.downstream_caller_from_input(&InputConnector::node(node_id, *import_index), &encapsulating_path) } - NodeInput::Inline(inline_rust) => None, + NodeInput::Inline(_) => None, }; let Some(caller_input) = caller_input else { - log::error!("Could not get compiled caller input for input: {:?}", input_connector); + log::error!("Could not get compiled caller input for input: {:?} in network: {:?}", input_connector, network_path); return None; }; Some(caller_input) @@ -631,7 +631,7 @@ impl NodeNetworkInterface { { 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); + let result = self.guess_type_from_node(child_id, child_input_index, &inner_path); inner_path.pop(); return result; } @@ -645,6 +645,10 @@ 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(NodeInput::Value { tagged_value, .. }) = self.input_from_connector(input_connector, network_path) { + return (tagged_value.ty(), TypeSource::TaggedValue); + } + 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))) @@ -657,17 +661,14 @@ impl NodeNetworkInterface { return (concrete!(()), TypeSource::Error("input connector is not a node")); }; - self.guess_type_from_node(node_id, input_connector.input_index(), network_path); + 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) { + pub fn output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { + if let Some(output_type) = self + .downstream_caller_from_output(output_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 (output_type.clone(), TypeSource::Compiled); } (concrete!(()), TypeSource::Error("Not compiled")) @@ -678,10 +679,10 @@ impl NodeNetworkInterface { } pub fn remove_type(&mut self, sni: SNI) { - self.resolved_types.remove(sni); + self.resolved_types.remove(&sni); } - pub fn set_node_caller(&mut self, node: &NodeId, caller: CompiledProtonodeInput, network_path: &[NodeId]) { + pub fn set_node_caller(&mut self, node_id: &NodeId, caller: CompiledProtonodeInput, network_path: &[NodeId]) { let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return; }; @@ -692,6 +693,7 @@ impl NodeNetworkInterface { match input_connector { InputConnector::Node { node_id, input_index } => { let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + log::error!("node metadata must exist when setting input caller for node {}, input index {}", node_id, input_index); return; }; let Some(input_metadata) = metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { @@ -770,84 +772,6 @@ impl NodeNetworkInterface { } } - /// Retrieves the output types for a given document node and its exports. - /// - /// This function traverses the node and its nested network structure (if applicable) to determine - /// the types of all outputs, including the primary output and any additional exports. - /// - /// # Arguments - /// - /// * `node` - A reference to the `DocumentNode` for which to determine output types. - /// * `resolved_types` - A reference to `ResolvedDocumentNodeTypes` containing pre-resolved type information. - /// * `node_id_path` - A slice of `NodeId`s representing the path to the current node in the document graph. - /// - /// # Returns - /// - /// A `Vec>` where: - /// - The first element is the primary output type of the node. - /// - Subsequent elements are types of additional exports (if the node is a network). - /// - `None` values indicate that a type couldn't be resolved for a particular output. - /// - /// # Behavior - /// - /// 1. Retrieves the primary output type from `resolved_types`. - /// 2. If the node is a network: - /// - Iterates through its exports (skipping the first/primary export). - /// - For each export, traverses the network until reaching a protonode or terminal condition. - /// - Determines the output type based on the final node/value encountered. - /// 3. Collects and returns all resolved types. - /// - /// # Note - /// - /// This function assumes that export indices and node IDs always exist within their respective - /// collections. It will panic if these assumptions are violated. - /// - pub fn output_type(&self, node_id: &NodeId, output_index: usize, network_path: &[NodeId]) -> (Type, TypeSource) { - let Some(implementation) = self.implementation(node_id, network_path) else { - log::error!("Could not get output type for node {node_id} output index {output_index}. This node is no longer supported, and needs to be upgraded."); - return (concrete!(()), TypeSource::Error("Could not get implementation")); - }; - - // If the node is not a protonode, get types by traversing across exports until a proto node is reached. - match &implementation { - graph_craft::document::DocumentNodeImplementation::Network(internal_network) => { - let Some(export) = internal_network.exports.get(output_index) else { - return (concrete!(()), TypeSource::Error("Could not get export index")); - }; - match export { - NodeInput::Node { - node_id: nested_node_id, - output_index, - .. - } => self.output_type(nested_node_id, *output_index, &[network_path, &[*node_id]].concat()), - NodeInput::Value { tagged_value, .. } => (tagged_value.ty(), TypeSource::TaggedValue), - NodeInput::Network { .. } => { - // let mut encapsulating_path = network_path.to_vec(); - // let encapsulating_node = encapsulating_path.pop().expect("No imports exist in document network"); - // self.input_type(&InputConnector::node(encapsulating_node, *import_index), network_path) - (concrete!(()), TypeSource::Error("Could not type from network")) - } - NodeInput::Scope(_) => todo!(), - NodeInput::Inline(_) => todo!(), - NodeInput::Reflection(_) => todo!(), - } - } - graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode) => { - let node_id_path = &[network_path, &[*node_id]].concat(); - self.resolved_types - .types - .get(node_id_path) - .map(|ty| (ty.output.clone(), TypeSource::Compiled)) - .or_else(|| { - let node_types = random_protonode_implementation(protonode)?; - Some((node_types.return_value.clone(), TypeSource::RandomProtonodeImplementation)) - }) - .unwrap_or((concrete!(()), TypeSource::Error("Could not get protonode implementation"))) - } - graph_craft::document::DocumentNodeImplementation::Extract => (concrete!(()), TypeSource::Error("extract node")), - } - } - pub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { let top_left_position = self .node_click_targets(node_id, network_path) @@ -1231,7 +1155,7 @@ impl NodeNetworkInterface { /// Returns the description of the node, or an empty string if it is not set. pub fn description(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { - self.get_node_definition(network_path, *node_id) + self.node_definition(*node_id, network_path) .map(|node_definition| node_definition.description.to_string()) .filter(|description| description != "TODO") .unwrap_or_default() @@ -1568,7 +1492,7 @@ impl NodeNetworkInterface { continue; }; nested_network.exports = old_network.exports; - nested_network.scope_injections = old_network.scope_injections.into_iter().collect(); + // nested_network.scope_injections = old_network.scope_injections.into_iter().collect(); let Some(nested_network_metadata) = network_metadata.nested_metadata_mut(&network_path) else { log::error!("Could not get nested network in from_old_network"); continue; @@ -1621,6 +1545,7 @@ impl NodeNetworkInterface { document_metadata: DocumentMetadata::default(), resolved_types: HashMap::new(), transaction_status: TransactionStatus::Finished, + current_hash: 0, } } } @@ -2789,7 +2714,7 @@ impl NodeNetworkInterface { let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0); let vertical_start: bool = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path)); let thick = vertical_end && vertical_start; - let vector_wire = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style); + let (vector_wire, _) = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style); let mut path_string = String::new(); let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); @@ -2799,6 +2724,8 @@ impl NodeNetworkInterface { data_type, thick, dashed: false, + center: None, + input_sni: None, }); Some(WirePathUpdate { @@ -2809,14 +2736,14 @@ impl NodeNetworkInterface { } /// Returns the vector subpath and a boolean of whether the wire should be thick. - pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath, bool)> { + pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath, bool, DVec2)> { let Some(input_position) = self.get_input_center(input, network_path) else { log::error!("Could not get dom rect for wire end: {:?}", input); return None; }; // An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector let Some(upstream_output) = self.upstream_output_connector(input, network_path) else { - return Some((Subpath::from_anchors(std::iter::empty(), false), false)); + return Some((Subpath::from_anchors(std::iter::empty(), false), false, DVec2::default())); }; let Some(output_position) = self.get_output_center(&upstream_output, network_path) else { log::error!("Could not get dom rect for wire start: {:?}", upstream_output); @@ -2825,19 +2752,23 @@ impl NodeNetworkInterface { let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0); let vertical_start = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path)); let thick = vertical_end && vertical_start; - Some((build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style), thick)) + let (wire, center) = build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style); + Some((wire, thick, center)) } pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option { - let (vector_wire, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?; + let (vector_wire, thick, center) = self.vector_wire_from_input(input, graph_wire_style, network_path)?; let mut path_string = String::new(); let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0); + let input_sni = self.downstream_caller_from_input(input, network_path).map(|caller| NodeId(caller.0.0 + caller.1 as u64)); Some(WirePath { path_string, data_type, thick, dashed, + center: Some((center.x, center.y)), + input_sni, }) } @@ -6562,7 +6493,6 @@ impl InputPersistentMetadata { struct InputTransientMetadata { wire: TransientMetadata, caller: Option, - input_type: Option, } // TODO: Eventually remove this migration document upgrade code diff --git a/editor/src/messages/portfolio/document/utility_types/wires.rs b/editor/src/messages/portfolio/document/utility_types/wires.rs index 9f85c8267..e37dc324a 100644 --- a/editor/src/messages/portfolio/document/utility_types/wires.rs +++ b/editor/src/messages/portfolio/document/utility_types/wires.rs @@ -12,6 +12,9 @@ pub struct WirePath { pub data_type: FrontendGraphDataType, pub thick: bool, pub dashed: bool, + pub center: Option<(f64, f64)>, + #[serde(rename = "inputSni")] + pub input_sni: Option, } #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] @@ -53,7 +56,7 @@ impl GraphWireStyle { } } -pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath { +pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> (Subpath, DVec2) { let grid_spacing = 24.; match graph_wire_style { GraphWireStyle::Direct => { @@ -85,7 +88,7 @@ pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing); let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing); - Subpath::new( + let subpath = Subpath::new( vec![ ManipulatorGroup { anchor: locations[0], @@ -113,7 +116,9 @@ pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical }, ], false, - ) + ); + let center = subpath.center().unwrap(); + (subpath, center) } GraphWireStyle::GridAligned => { let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in); @@ -446,13 +451,13 @@ fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_o vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)] } -fn straight_wire_subpath(locations: Vec) -> Subpath { +fn straight_wire_subpath(locations: Vec) -> (Subpath, DVec2) { if locations.is_empty() { - return Subpath::new(Vec::new(), false); + return (Subpath::new(Vec::new(), false), DVec2::default()); } if locations.len() == 2 { - return Subpath::new( + let subpath = Subpath::new( vec![ ManipulatorGroup { anchor: locations[0].into(), @@ -469,6 +474,8 @@ fn straight_wire_subpath(locations: Vec) -> Subpath { ], false, ); + let center = subpath.center().unwrap(); + return (subpath, center); } let corner_radius = 10; @@ -585,5 +592,7 @@ fn straight_wire_subpath(locations: Vec) -> Subpath { out_handle: None, id: PointId::generate(), }); - Subpath::new(path, false) + let subpath = Subpath::new(path, false); + let center = subpath.center().unwrap(); + (subpath, center) } diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 96213b4ee..acdfdbf8d 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -3,12 +3,13 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector}; +use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::prelude::DocumentMessageHandler; use bezier_rs::Subpath; use glam::IVec2; use graph_craft::document::DocumentNode; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; +use graph_craft::document::{InputConnector, OutputConnector}; use graphene_std::ProtoNodeIdentifier; use graphene_std::text::TypesettingConfig; use graphene_std::uuid::NodeId; @@ -492,7 +493,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ } }); - for (node_id, node, network_path) in network.recursive_nodes() { + for (node_path, node) in network.recursive_nodes() { + let (node_id, network_path) = node_path.split_last().unwrap(); if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation { let node_path_without_type_args = protonode_id.name.split('<').next(); if let Some(new) = node_path_without_type_args.and_then(|node_path| replacements.get(node_path)) { @@ -509,9 +511,9 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ .network_interface .document_network() .recursive_nodes() - .map(|(node_id, node, path)| (*node_id, node.clone(), path)) - .collect::)>>(); - for (node_id, node, network_path) in &nodes { + .map(|(node_path, node)| (node_path, node.clone())) + .collect::, graph_craft::document::DocumentNode)>>(); + for (node_path, node) in &nodes { migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open); } } @@ -523,7 +525,6 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template()); } } - // Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { document diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 9eaccf542..fd9418ac9 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -1,17 +1,15 @@ -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 crate::node_graph_executor::CompilationResponse; +use crate::node_graph_executor::IntrospectionResponse; use graph_craft::document::CompilationMetadata; +use graphene_std::Color; 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)] @@ -24,11 +22,24 @@ pub enum PortfolioMessage { #[child] Spreadsheet(SpreadsheetMessage), + // Introspected data is cleared after all queued messages which relied on the introspection are complete + ClearIntrospectedData, + // 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 + // Sends a request to evaluate the network. Should occur when any context value changes. EvaluateActiveDocument, - + // Sends a request to introspect data in the network, and return it to the editor + IntrospectActiveDocument { + inputs_to_introspect: HashSet, + }, + ExportActiveDocument { + file_name: String, + file_type: FileType, + scale_factor: f64, + bounds: ExportBounds, + transparent_background: bool, + }, // 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 { @@ -36,12 +47,13 @@ pub enum PortfolioMessage { }, ProcessEvaluationResponse { evaluation_metadata: RenderMetadata, + }, + ProcessIntrospectionResponse { #[serde(skip)] - introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box)>, - }, - ProcessThumbnails { - inputs_to_render: HashSet, + introspected_inputs: IntrospectionResponse, }, + RenderThumbnails, + ProcessThumbnails, DocumentPassMessage { document_id: DocumentId, message: DocumentMessage, @@ -134,13 +146,6 @@ pub enum PortfolioMessage { SelectDocument { document_id: DocumentId, }, - SubmitDocumentExport { - file_name: String, - file_type: FileType, - scale_factor: f64, - bounds: ExportBounds, - transparent_background: bool, - }, ToggleRulers, UpdateDocumentWidgets, UpdateOpenDocumentsList, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index a0b3d6a5f..5b0ee5334 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -6,24 +6,29 @@ use crate::application::generate_uuid; 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::frontend::utility_types::{ExportBounds, 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::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; 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::portfolio::spreadsheet::SpreadsheetMessageHandlerData; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; -use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; +use crate::node_graph_executor::{CompilationRequest, ExportConfig, NodeGraphExecutor}; use glam::{DAffine2, DVec2}; -use graph_craft::document::NodeId; -use graphene_std::renderer::Quad; +use graph_craft::document::value::EditorMetadata; +use graph_craft::document::{AbsoluteInputConnector, InputConnector, NodeInput, OutputConnector}; +use graphene_std::any::EditorContext; +use graphene_std::application_io::TimingInformation; +use graphene_std::memo::IntrospectMode; +use graphene_std::renderer::{Quad, RenderMetadata}; use graphene_std::text::Font; -use std::vec; +use graphene_std::transform::{Footprint, RenderQuality}; +use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; +use std::sync::Arc; #[derive(ExtractField)] pub struct PortfolioMessageContext<'a> { @@ -54,9 +59,10 @@ pub struct PortfolioMessageHandler { 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, + // Always take data after requesting it + pub introspected_data: HashMap>>, + pub introspected_call_argument: HashMap>>, + pub previous_thumbnail_data: HashMap>, } #[message_handler_data] @@ -105,13 +111,18 @@ impl MessageHandler> for Portfolio self.menu_bar_message_handler.process_message(message, responses, ()); } PortfolioMessage::Spreadsheet(message) => { - self.spreadsheet.process_message(message, responses, SpreadsheetMessageHandlerData {introspected_data}); + self.spreadsheet.process_message( + message, + responses, + SpreadsheetMessageHandlerData { + introspected_data: &self.introspected_data, + }, + ); } PortfolioMessage::Document(message) => { if let Some(document_id) = self.active_document_id { if let Some(document) = self.documents.get_mut(&document_id) { - let document_inputs = DocumentMessageContext { - document_id, + let document_inputs = DocumentMessageData { ipp, persistent_data: &self.persistent_data, current_tool, @@ -143,8 +154,7 @@ impl MessageHandler> for Portfolio } PortfolioMessage::DocumentPassMessage { document_id, message } => { if let Some(document) = self.documents.get_mut(&document_id) { - let document_inputs = DocumentMessageContext { - document_id, + let document_inputs = DocumentMessageData { ipp, persistent_data: &self.persistent_data, current_tool, @@ -356,12 +366,10 @@ impl MessageHandler> for Portfolio PortfolioMessage::NewDocumentWithName { name } => { let mut new_document = DocumentMessageHandler::default(); new_document.name = name; - responses.add(DocumentMessage::PTZUpdate); let document_id = DocumentId(generate_uuid()); if self.active_document().is_some() { responses.add(BroadcastEvent::ToolAbort); - responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); } self.load_document(new_document, document_id, responses, false); @@ -664,13 +672,10 @@ 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::StartQueue); + responses.add(Message::StartEvaluationQueue); 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::StartQueue); - responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); + responses.add(DocumentMessage::ZoomCanvasToFitAll); + responses.add(Message::EndEvaluationQueue); } } PortfolioMessage::PasteSvg { @@ -696,13 +701,10 @@ 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::StartQueue); + responses.add(Message::StartEvaluationQueue); 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::StartQueue); - responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); + responses.add(DocumentMessage::ZoomCanvasToFitAll); + responses.add(Message::EndEvaluationQueue); } } PortfolioMessage::PrevDocument => { @@ -747,7 +749,6 @@ impl MessageHandler> for Portfolio responses.add(OverlaysMessage::Draw); responses.add(BroadcastEvent::ToolAbort); responses.add(BroadcastEvent::SelectionChanged); - responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); responses.add(PortfolioMessage::CompileActiveDocument); responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open }); if node_graph_open { @@ -768,8 +769,8 @@ impl MessageHandler> for Portfolio } } 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); + let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get_mut(&document_id)) else { + log::error!("Tried to render non-existent document: {:?}", self.active_document_id); return; }; if document.network_interface.hash_changed() { @@ -788,37 +789,49 @@ impl MessageHandler> for Portfolio }, }); } - // 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 { + let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get_mut(&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 (value_connectors, caller) in compilation_metadata.protonode_caller_for_values { + for AbsoluteInputConnector { network_path, connector } in value_connectors { + let (first, network_path) = network_path.split_first().unwrap(); + if first != &NodeId(0) { + continue; + } + 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 (protonode_paths, caller) in compilation_metadata.protonode_caller_for_nodes { + for protonode_path in protonode_paths { + let (first, node_path) = protonode_path.split_first().unwrap(); + if first != &NodeId(0) { + continue; + } + let (node_id, network_path) = node_path.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 + let mut cleared_thumbnails = Vec::new(); + for (sni, number_of_inputs) in compilation_metadata.types_to_remove { + // Removed saved 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)); + // Remove all thumbnails + for input_index in 0..number_of_inputs { + cleared_thumbnails.push(NodeId(sni.0 + input_index as u64 + 1)); } - responses.add(FrontendMessage::UpdateThumbnails { add: Vec::new(), clear: cleared_thumbnails }) } + responses.add(FrontendMessage::UpdateThumbnails { + add: Vec::new(), + clear: cleared_thumbnails, + }); + // Always evaluate after a recompile + responses.add(PortfolioMessage::EvaluateActiveDocument); } PortfolioMessage::EvaluateActiveDocument => { let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { @@ -827,100 +840,54 @@ impl MessageHandler> for Portfolio }; // 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) - } - } - }, - _ => {}, - } + // 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(&upstream_node, &[]).and_then(|reference| reference.as_deref()); + // if reference == Some("Path") || reference == Some("Transform") { + // let input_connector = InputConnector::Node { node_id: *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.insert((*downstream_caller, IntrospectMode::Data)); + // } + // } + // } + // _ => {} + // } // 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 }); - } - } + // 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, + // }; + // match document.network_interface.downstream_caller_from_input(&selected_connector, selected_network_path) { + // Some(caller) => { + // inputs_to_monitor.insert((*caller, IntrospectMode::Data)); + // inspect_input = Some(InspectInputConnector { + // input_connector: AbsoluteInputConnector { + // network_path: selected_network_path.clone(), + // connector: selected_connector, + // }, + // protonode_input: *caller, + // }); + // } + // None => log::error!("Could not get downstream caller for {:?}", selected_connector), + // } + // }; + // } // let animation_time = match animation.timing_information().animation_time { // AnimationState::Stopped => 0., @@ -929,67 +896,18 @@ impl MessageHandler> for Portfolio // }; 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.footprint = Some(Footprint { + transform: document.metadata().document_to_viewport, + resolution: ipp.viewport_bounds.size().as_uvec2(), + quality: RenderQuality::Full, + }); + context.animation_time = Some(timing_information.animation_time.as_secs_f64()); + context.real_time = Some(ipp.time); + context.downstream_transform = Some(DAffine2::IDENTITY); - 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); + self.executor.submit_node_graph_evaluation(context, None, None); } - 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); - }, - } - } - + PortfolioMessage::ProcessEvaluationResponse { evaluation_metadata } => { let RenderMetadata { upstream_footprints: footprints, local_transforms, @@ -1009,25 +927,117 @@ impl MessageHandler> for Portfolio // 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)); - }, + + // After an evaluation, always render all thumbnails + responses.add(PortfolioMessage::RenderThumbnails); + } + PortfolioMessage::IntrospectActiveDocument { inputs_to_introspect } => { + self.executor.submit_node_graph_introspection(inputs_to_introspect); + } + PortfolioMessage::ProcessIntrospectionResponse { introspected_inputs } => { + for (input, mode, data) in introspected_inputs.0.into_iter() { + match mode { + IntrospectMode::Input => { + self.introspected_call_argument.insert(input, data); + } + IntrospectMode::Data => { + self.introspected_data.insert(input, data); + } } } - responses.add(FrontendMessage::UpdateThumbnails { add: thumbnail_response.add, clear: thumbnail_response.clear }) - }, - PortfolioMessage::ActiveDocumentExport { + } + PortfolioMessage::ClearIntrospectedData => { + self.introspected_call_argument.clear(); + self.introspected_data.clear() + } + PortfolioMessage::RenderThumbnails => { + 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; + }; + let mut inputs_to_render = HashSet::new(); + + // 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_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() { + match document + .network_interface + .downstream_caller_from_input(&InputConnector::Export(export_index), &document.breadcrumb_network_path) + { + Some(caller) => { + // inputs_to_monitor.insert((*caller, IntrospectMode::Data)); + inputs_to_render.insert(*caller); + } + None => {} + }; + if let 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(upstream_node, index), &document.breadcrumb_network_path) + { + // inputs_to_monitor.insert((*caller, IntrospectMode::Data)); + inputs_to_render.insert(*caller); + }; + } + } + } + } + }; + + responses.add(PortfolioMessage::IntrospectActiveDocument { + inputs_to_introspect: inputs_to_render, + }); + responses.add(Message::StartIntrospectionQueue); + responses.add(PortfolioMessage::ProcessThumbnails); + responses.add(Message::EndIntrospectionQueue); + } + PortfolioMessage::ProcessThumbnails => { + let mut thumbnail_response = ThumbnailRenderResponse::default(); + for (thumbnail_input, introspected_data) in self.introspected_data.drain() { + let input_node_id = thumbnail_input.0.0 + thumbnail_input.1 as u64; + + let Some(evaluated_data) = introspected_data else { + // Input was not evaluated, do not change its thumbnail + continue; + }; + + let previous_thumbnail_data = self.previous_thumbnail_data.get(&thumbnail_input); + + match graph_craft::document::value::render_thumbnail_if_change(&evaluated_data, previous_thumbnail_data) { + graph_craft::document::value::ThumbnailRenderResult::NoChange => return, + graph_craft::document::value::ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(NodeId(input_node_id)), + graph_craft::document::value::ThumbnailRenderResult::UpdateThumbnail(thumbnail) => thumbnail_response.add.push((NodeId(input_node_id), thumbnail)), + } + self.previous_thumbnail_data.insert(thumbnail_input, evaluated_data); + } + responses.add(FrontendMessage::UpdateThumbnails { + add: thumbnail_response.add, + clear: thumbnail_response.clear, + }) + } + PortfolioMessage::ExportActiveDocument { file_name, file_type, - animation_export_data, scale_factor, bounds, transparent_background, @@ -1035,57 +1045,45 @@ impl MessageHandler> for Portfolio 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, - // }, - // }); + 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 { + let Some(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())?; + // ExportBounds::Viewport => ipp.document_bounds(document.metadata().document_to_viewport), + }) else { + log::error!("No bounding box when exporting"); + return; + }; 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); + context.footprint = Some(Footprint { + transform: 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) { @@ -1093,28 +1091,18 @@ impl MessageHandler> for Portfolio false => file_name + file_suffix, }; - let export_config = ExportConfig { - file_name, - file_type, - scale_factor, - bounds, - transparent_background, - ..Default::default() - }; - self.executor.submit_node_graph_evaluation( context, - Vec::new(), - None, - Some(ExportConfig { - file_name, - file_type, - scale_factor, - bounds, - transparent_background, - size: scaled_size, - }), - ); + None, + Some(ExportConfig { + file_name, + file_type, + scale_factor, + bounds, + transparent_background, + size: scaled_size, + }), + ); // if let Some((start, end, fps)) = animation_export_data { // let total_frames = ((start - end) * fps) as u32; @@ -1155,20 +1143,20 @@ impl MessageHandler> for Portfolio // } // 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, - // }, - // }); + 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, + }, + }); } PortfolioMessage::ToggleRulers => { @@ -1181,7 +1169,7 @@ impl MessageHandler> for Portfolio } PortfolioMessage::UpdateDocumentWidgets => { if let Some(document) = self.active_document() { - document.update_document_widgets(responses, animation.is_playing(), animation_time); + document.update_document_widgets(responses, animation.is_playing(), timing_information.animation_time); } } PortfolioMessage::UpdateOpenDocumentsList => { @@ -1333,57 +1321,11 @@ 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::ProcessQueue((graphene_std::renderer::EvaluationMetadata::default(), Vec::new()))); + responses.add(Message::ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata::default())); responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error }); } result } - - // 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 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, - }; - - // Render the thumbnail data into an SVG string - let mut render = SvgRender::new(); - renderable_data.render_svg(&mut render, &render_params); - - // 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)] @@ -1391,10 +1333,3 @@ 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 2d2e37534..225520216 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message.rs @@ -1,3 +1,4 @@ +use crate::messages::prelude::*; use graph_craft::document::AbsoluteInputConnector; use graphene_std::uuid::CompiledProtonodeInput; @@ -7,7 +8,7 @@ use graphene_std::uuid::CompiledProtonodeInput; pub enum SpreadsheetMessage { ToggleOpen, - UpdateLayout { inpect_input: InspectInputConnector }, + UpdateLayout { inspect_input: InspectInputConnector }, PushToInstancePath { index: usize }, TruncateInstancePath { len: usize }, @@ -15,7 +16,7 @@ pub enum SpreadsheetMessage { ViewVectorDataDomain { domain: VectorDataDomain }, } -#[derive(PartialEq, Eq, Clone, Copy, Default, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)] pub enum VectorDataDomain { #[default] Points, diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index ec9c27289..613c22dfd 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -3,21 +3,17 @@ use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, use crate::messages::portfolio::spreadsheet::InspectInputConnector; use crate::messages::prelude::*; use crate::messages::tool::tool_messages::tool_prelude::*; -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>; +pub struct SpreadsheetMessageHandlerData<'a> { + pub introspected_data: &'a HashMap>>, } /// The spreadsheet UI allows for instance data to be previewed. @@ -35,7 +31,7 @@ pub struct SpreadsheetMessageHandler { #[message_handler_data] impl MessageHandler for SpreadsheetMessageHandler { fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, data: SpreadsheetMessageHandlerData) { - let {introspected_data} = data; + let SpreadsheetMessageHandlerData { introspected_data } = data; match message { SpreadsheetMessage::ToggleOpen => { self.spreadsheet_view_open = !self.spreadsheet_view_open; @@ -48,12 +44,12 @@ impl MessageHandler for Sprea } // Update checked UI state for open responses.add(MenuBarMessage::SendLayout); - self.update_layout(responses); + self.update_layout(introspected_data, 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); + SpreadsheetMessage::UpdateLayout { inspect_input } => { + self.inspect_input = Some(inspect_input); self.update_layout(introspected_data, responses); } @@ -79,7 +75,7 @@ impl MessageHandler for Sprea } impl SpreadsheetMessageHandler { - fn update_layout(&mut self, introspected_data: &HashMap>, responses: &mut VecDeque) { + fn update_layout(&mut self, introspected_data: &HashMap>>, responses: &mut VecDeque) { responses.add(FrontendMessage::UpdateSpreadsheetState { // The node is sent when the data is available node: None, @@ -94,18 +90,20 @@ impl SpreadsheetMessageHandler { breadcrumbs: Vec::new(), vector_data_domain: self.viewing_vector_data_domain, }; - let mut layout = match self.inspect_input { + 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) { + match introspected_data.get(&inspect_input.protonode_input) { + Some(data) => match data { + Some(instrospected_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("Introspected data is not available for this input. This input may be cached."), + // There should always be an entry for each protonode input. If its empty then it was not requested or an error occured + None => label("Error getting introspected data"), } - }, + } None => label("No input selected to show data for."), }; @@ -130,7 +128,7 @@ struct LayoutData<'a> { vector_data_domain: VectorDataDomain, } -fn generate_layout(introspected_data: &Box, data: &mut LayoutData) -> Option> { +fn generate_layout(introspected_data: &Arc, data: &mut LayoutData) -> Option> { // We simply try random types. TODO: better strategy. #[allow(clippy::manual_map)] if let Some(io) = introspected_data.downcast_ref::() { diff --git a/editor/src/messages/portfolio/utility_types.rs b/editor/src/messages/portfolio/utility_types.rs index 13cc7b9fe..b719ae147 100644 --- a/editor/src/messages/portfolio/utility_types.rs +++ b/editor/src/messages/portfolio/utility_types.rs @@ -2,7 +2,7 @@ use graphene_std::text::FontCache; #[derive(Debug, Default)] pub struct PersistentData { - pub font_cache: Arc, + pub font_cache: std::sync::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 8149959a6..aa2d11daa 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -20,10 +20,8 @@ impl PreferencesMessageHandler { self.selection_mode } - pub fn editor_preferences(&self) -> EditorPreferences { - EditorPreferences { - use_vello: self.use_vello && self.supports_wgpu(), - } + pub fn use_vello(&self) -> bool { + self.use_vello && self.supports_wgpu() } pub fn supports_wgpu(&self) -> bool { 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 3ff2f52c1..3e481f144 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 @@ -3,16 +3,15 @@ use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::message::Message; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; -use crate::messages::prelude::Responses; +use crate::messages::prelude::{PortfolioMessage, Responses}; use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_polygon_parameters, inside_polygon, inside_star, polygon_outline, polygon_vertex_position, star_outline}; use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position}; use glam::{DAffine2, DVec2}; -use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graph_craft::document::{InputConnector, NodeInput}; use std::collections::VecDeque; use std::f64::consts::TAU; 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 794131cfd..39a447b0a 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 @@ -3,15 +3,15 @@ use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD}; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::message::Message; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}; -use crate::messages::prelude::FrontendMessage; +use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext}; +use crate::messages::prelude::{FrontendMessage, PortfolioMessage}; use crate::messages::prelude::Responses; use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline}; use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position}; use glam::DVec2; -use graph_craft::document::NodeInput; +use graph_craft::document::{InputConnector, NodeInput}; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2}; 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 cd5680597..07392a604 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -1,12 +1,12 @@ 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::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate}; +use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate}; use crate::messages::prelude::*; use bezier_rs::Subpath; use glam::DVec2; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::{InputConnector, NodeInput}; use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::Color; use graphene_std::NodeInputDecleration; @@ -154,8 +154,9 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde }); responses.add(PortfolioMessage::CompileActiveDocument); - responses.add(Message::StartQueue); + responses.add(Message::StartEvaluationQueue); responses.add(PenToolMessage::RecalculateLatestPointsPosition); + responses.add(Message::EndEvaluationQueue); } /// Merge the `first_endpoint` with `second_endpoint`. diff --git a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs index 700f3c277..f50947efa 100644 --- a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs @@ -3,11 +3,11 @@ use super::*; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; +use crate::messages::portfolio::document::utility_types::network_interface::{ NodeTemplate}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; -use graph_craft::document::NodeInput; +use graph_craft::document::{InputConnector, NodeInput}; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; 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 aed73cfa7..356e2f3b8 100644 --- a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs @@ -3,14 +3,14 @@ use crate::consts::{BOUNDS_SELECT_THRESHOLD, LINE_ROTATE_SNAP_ANGLE}; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; +use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::tool::common_functionality::graph_modification_utils; pub use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapTypeConfiguration}; use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DVec2; -use graph_craft::document::NodeInput; +use graph_craft::document::{InputConnector, NodeInput}; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; diff --git a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs index 82dcf10cf..3bfe8ce84 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -5,7 +5,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; +use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDial; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDialState; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle; @@ -16,6 +16,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGiz use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; +use graph_craft::document::InputConnector; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; diff --git a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs index cbd672296..a6b2d382d 100644 --- a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs @@ -3,11 +3,11 @@ use super::*; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; +use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; -use graph_craft::document::NodeInput; +use graph_craft::document::{InputConnector, NodeInput}; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 955984150..a6379237b 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -2,7 +2,6 @@ use super::ShapeToolData; use crate::messages::message::Message; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage, Responses}; use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::shape_editor::ShapeState; @@ -11,7 +10,7 @@ use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; use bezier_rs::Subpath; use glam::{DAffine2, DMat2, DVec2}; -use graph_craft::document::NodeInput; +use graph_craft::document::{InputConnector, NodeInput}; use graph_craft::document::value::TaggedValue; use graphene_std::vector::click_target::ClickTargetType; use graphene_std::vector::misc::dvec2_to_point; diff --git a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs index 653b22f3b..4ba04c7ec 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -4,7 +4,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; +use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState}; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState}; use crate::messages::tool::common_functionality::graph_modification_utils; @@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGi use crate::messages::tool::tool_messages::tool_prelude::*; use core::f64; use glam::DAffine2; -use graph_craft::document::NodeInput; +use graph_craft::document::{InputConnector, NodeInput}; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 810cb4636..a20331768 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -10,8 +10,8 @@ use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint; use crate::messages::tool::common_functionality::snapping::SnapData; use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::transformation_cage::*; -use graph_craft::document::NodeId; use graphene_std::renderer::Quad; +use graphene_std::uuid::NodeId; #[derive(Default, ExtractField)] pub struct ArtboardTool { diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 9446f9713..07b11398c 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -5,11 +5,11 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::FlowType; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use graph_craft::document::NodeId; use graph_craft::document::value::TaggedValue; use graphene_std::Color; use graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle}; use graphene_std::raster::BlendMode; +use graphene_std::uuid::NodeId; const BRUSH_MAX_SIZE: f64 = 5000.; @@ -379,8 +379,9 @@ impl Fsm for BrushToolFsmState { else { new_brush_layer(document, responses); responses.add(PortfolioMessage::CompileActiveDocument); - responses.add(Message::StartQueue); + responses.add(Message::StartEvaluationQueue); responses.add(BrushToolMessage::DragStart); + responses.add(Message::EndEvaluationQueue); 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 c8a2ccbfc..aa453a2ff 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -8,7 +8,7 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::utility_functions::should_extend; use glam::DVec2; -use graph_craft::document::NodeId; +use graphene_std::uuid::NodeId; use graphene_std::Color; use graphene_std::vector::VectorModificationType; use graphene_std::vector::{PointId, SegmentId}; @@ -251,7 +251,6 @@ 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::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/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index a8b10afaa..cdc8e1559 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -643,12 +643,12 @@ impl PathToolData { self.drag_start_pos = input.mouse.position; - if input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS { + if input.time as u64 - self.last_click_time > DOUBLE_CLICK_MILLISECONDS { self.saved_points_before_anchor_convert_smooth_sharp.clear(); self.stored_selection = None; } - self.last_click_time = input.time; + self.last_click_time = input.time as u64; let old_selection = shape_editor.selected_points().cloned().collect::>(); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 74bc9e199..1b01476ce 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -12,8 +12,8 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend}; use bezier_rs::{Bezier, BezierHandles}; -use graph_craft::document::NodeId; use graphene_std::Color; +use graphene_std::uuid::NodeId; use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData}; use graphene_std::vector::{PointId, VectorModificationType}; @@ -1258,9 +1258,10 @@ 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::StartQueue); + responses.add(Message::StartEvaluationQueue); // It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky) responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport }); + responses.add(Message::EndEvaluationQueue); } /// Perform extension of an existing path diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index a5d704f5a..d9d0ea5aa 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -22,11 +22,11 @@ use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage}; use bezier_rs::Subpath; use glam::DMat2; -use graph_craft::document::NodeId; use graphene_std::path_bool::BooleanOperation; use graphene_std::renderer::Quad; use graphene_std::renderer::Rect; use graphene_std::transform::ReferencePoint; +use graphene_std::uuid::NodeId; use std::fmt; #[derive(Default, ExtractField)] diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 02da4bad3..b4f246cf6 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -3,7 +3,6 @@ use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager; @@ -19,9 +18,10 @@ use crate::messages::tool::common_functionality::snapping::{self, SnapCandidateP use crate::messages::tool::common_functionality::transformation_cage::{BoundingBoxManager, EdgeBool}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage}; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::{InputConnector, NodeInput}; use graphene_std::Color; use graphene_std::renderer::Quad; +use graphene_std::uuid::NodeId; use graphene_std::vector::misc::ArcType; use std::vec; @@ -599,17 +599,16 @@ 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::StartQueue); - match tool_data.current_shape { ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => { + responses.add(Message::StartEvaluationQueue); responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), transform_in: TransformIn::Viewport, skip_rerender: false, }); - + responses.add(Message::EndEvaluationQueue); tool_options.fill.apply_fill(layer, responses); } ShapeType::Line => { @@ -617,6 +616,7 @@ impl Fsm for ShapeToolFsmState { tool_data.line_data.editing_layer = Some(layer); } } + tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index af5504828..cf1a60c55 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -10,7 +10,8 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio use crate::messages::tool::common_functionality::graph_modification_utils::{self, find_spline, merge_layers, merge_points}; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::NodeInput; +use graphene_std::uuid::NodeId; use graphene_std::Color; use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; @@ -360,8 +361,6 @@ impl Fsm for SplineToolFsmState { tool_options.stroke.apply_stroke(tool_data.weight, layer, responses); tool_data.current_layer = Some(layer); - responses.add(Message::StartQueue); - SplineToolFsmState::Drawing } (SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => { diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index ded042256..c951f6520 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -5,7 +5,6 @@ use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_RED, DRAG_THRESHOLD}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name}; @@ -14,10 +13,11 @@ use crate::messages::tool::common_functionality::snapping::{self, SnapCandidateP use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::utility_functions::text_bounding_box; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::{InputConnector, NodeInput}; use graphene_std::Color; use graphene_std::renderer::Quad; use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font}; +use graphene_std::uuid::NodeId; use graphene_std::vector::style::Fill; #[derive(Default, ExtractField)] @@ -381,7 +381,7 @@ impl TextToolData { parent: document.new_layer_parent(true), insert_index: 0, }); - responses.add(Message::StartQueue); + responses.add(Message::StartEvaluationQueue); responses.add(GraphOperationMessage::FillSet { layer: self.layer, fill: if editing_text.color.is_some() { @@ -394,15 +394,14 @@ impl TextToolData { layer: self.layer, transform: editing_text.transform, transform_in: TransformIn::Viewport, - skip_rerender: true, + skip_rerender: false, }); self.editing_text = Some(editing_text); self.set_editing(true, font_cache, responses); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] }); - - responses.add(PortfolioMessage::CompileActiveDocument); + responses.add(Message::EndEvaluationQueue); } fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 2e3bc5057..785ecd03c 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -1,32 +1,18 @@ use std::sync::Arc; -use crate::consts::FILE_SAVE_SUFFIX; -use crate::messages::frontend::utility_types::{ExportBounds, FileType}; +use crate::messages::frontend::utility_types::FileType; use crate::messages::prelude::*; 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::document::value::{EditorMetadata, RenderOutput, TaggedValue}; +use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid}; use graph_craft::proto::GraphErrors; -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::any::EditorContext; 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::renderer::format_transform_matrix; use graphene_std::text::FontCache; -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 graphene_std::wasm_application_io::NetworkOutput; -use graphene_std::{CompiledProtonodeInput, OwnedContextImpl, SNI}; +use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; mod runtime_io; -use interpreted_executor::dynamic_executor::{EditorContext, ResolvedDocumentNodeMetadata}; pub use runtime_io::NodeRuntimeIO; mod runtime; @@ -35,7 +21,7 @@ pub use runtime::*; #[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) + // Data which is available from scope inputs pub font_cache: Arc, pub editor_metadata: EditorMetadata, } @@ -46,27 +32,34 @@ pub struct CompilationResponse { } // Metadata the editor sends when evaluating the network -#[derive(Debug, Default, DynAny)] +#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)] pub struct EvaluationRequest { pub evaluation_id: u64, - pub inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>, + #[serde(skip)] pub context: EditorContext, - // pub custom_node_to_evaluate: Option, + pub 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, +} + +#[derive(Debug, Clone, Default)] +pub struct IntrospectionResponse(pub Vec<((NodeId, usize), IntrospectMode, Option>)>); + +impl PartialEq for IntrospectionResponse { + fn eq(&self, _other: &Self) -> bool { + false + } } // #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] pub enum NodeGraphUpdate { CompilationResponse(CompilationResponse), EvaluationResponse(EvaluationResponse), + IntrospectionResponse(IntrospectionResponse), } #[derive(Debug, Default)] @@ -98,6 +91,7 @@ impl NodeGraphExecutor { let node_runtime = NodeRuntime::new(request_receiver, response_sender); let node_executor = Self { + busy: false, futures: HashMap::new(), runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver), }; @@ -118,34 +112,39 @@ impl NodeGraphExecutor { /// 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()); + if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::CompilationRequest(compilation_request)) { + log::error!("Could not send evaluation request. {:?}", error); + return; + } } - /// Adds an evaluate request for whatever current network is cached. - pub fn submit_node_graph_evaluation( - &mut self, - context: EditorContext, - inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>, - custom_node_to_evaluate: Option, - export_config: Option, - ) { + /// Adds an evaluation request for whatever current network is cached. + pub fn submit_node_graph_evaluation(&mut self, context: EditorContext, 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()); + if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::EvaluationRequest(EvaluationRequest { + evaluation_id, + context, + node_to_evaluate, + })) { + log::error!("Could not send evaluation request. {:?}", error); + return; + } let evaluation_context = EvaluationContext { export_config }; self.futures.insert(evaluation_id, evaluation_context); } + pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet) { + if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::IntrospectionRequest(nodes_to_introspect)) { + log::error!("Could not send evaluation request. {:?}", error); + return; + } + } + // 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> { - // Moved into portfolio message handler, since this is where the introspected inputs are saved for response in self.runtime_io.receive() { match response { - NodeGraphUpdate::EvaluationResponse(EvaluationResponse { - evaluation_id, - result, - transform, - introspected_inputs, - }) => { + NodeGraphUpdate::EvaluationResponse(EvaluationResponse { evaluation_id, result }) => { responses.add(OverlaysMessage::Draw); let node_graph_output = match result { @@ -160,18 +159,14 @@ impl NodeGraphExecutor { 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()); + return Err(format!("Incorrect render type for exporting {:?} (expected NetworkOutput)", value.ty())); } }; 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 { + let graphene_std::wasm_application_io::RenderOutputType::Svg(svg) = render_output.data else { return Err("Incorrect render type for exporting (expected RenderOutput::Svg)".to_string()); }; @@ -193,7 +188,7 @@ impl NodeGraphExecutor { } } else { // Update artwork - self.process_node_graph_output(render_output, introspected_inputs, transform, responses); + self.process_node_graph_output(render_output, responses)? } } NodeGraphUpdate::CompilationResponse(compilation_response) => { @@ -213,58 +208,35 @@ impl NodeGraphExecutor { Ok(result) => result, }; responses.add(PortfolioMessage::ProcessCompilationResponse { compilation_metadata }); - responses.add(NodeGraphMessage::SendGraph); + } + NodeGraphUpdate::IntrospectionResponse(introspection_response) => { + responses.add(Message::ProcessIntrospectionQueue(introspection_response)); } } } Ok(()) } - 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) => { - match render_output.data { - graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => { - // Send to frontend - responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); - } - graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => { - let matrix = format_transform_matrix(frame.transform); - let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{}\"", matrix) }; - let svg = format!( - r#"
"#, - frame.resolution.x, frame.resolution.y, frame.surface_id.0 - ); - responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); - } - _ => { - return Err(format!("Invalid node graph output type: {:#?}", render_output.data)); - } - } - - render_output_metadata = render_output.metadata; + fn process_node_graph_output(&self, render_output: RenderOutput, responses: &mut VecDeque) -> Result<(), String> { + match render_output.data { + graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => { + // Send to frontend + responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); + } + graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => { + let matrix = format_transform_matrix(frame.transform); + let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{}\"", matrix) }; + let svg = format!( + r#"
"#, + frame.resolution.x, frame.resolution.y, frame.surface_id.0 + ); + responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); } - // 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:#?}")); + return Err(format!("Invalid node graph output type: {:#?}", render_output.data)); } - }; - responses.add(Message::ProcessQueue((render_output_metadata, introspected_inputs))); + } + responses.add(Message::ProcessEvaluationQueue(render_output.metadata)); Ok(()) } } @@ -404,3 +376,35 @@ impl NodeGraphExecutor { // } // } // } + +// Passed as a scope input +#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +pub struct EditorMetadata { + // pub imaginate_hostname: String, + pub use_vello: bool, + pub hide_artboards: bool, + // If exporting, hide the artboard name and do not collect metadata + pub for_export: bool, + pub view_mode: graphene_core::vector::style::ViewMode, + pub transform_to_viewport: bool, +} + +unsafe impl dyn_any::StaticType for EditorMetadata { + type Static = EditorMetadata; +} + +impl Default for EditorMetadata { + fn default() -> Self { + Self { + // imaginate_hostname: "http://localhost:7860/".into(), + #[cfg(target_arch = "wasm32")] + use_vello: false, + #[cfg(not(target_arch = "wasm32"))] + use_vello: true, + hide_artboards: false, + for_export: false, + view_mode: graphene_core::vector::style::ViewMode::Normal, + transform_to_viewport: true, + } + } +} diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 8b119badb..29ceecc77 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -1,24 +1,12 @@ use super::*; use crate::messages::frontend::utility_types::{ExportBounds, FileType}; -use glam::{DAffine2, DVec2}; -use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeNetwork}; -use graph_craft::graphene_compiler::Compiler; +use glam::DVec2; +use graph_craft::document::NodeNetwork; use graph_craft::proto::GraphErrors; -use graph_craft::wasm_application_io::EditorPreferences; -use graph_craft::{ProtoNodeIdentifier, concrete}; -use graphene_std::Context; -use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; -use graphene_std::instances::Instance; -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}; -use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta}; +use graphene_std::uuid::CompiledProtonodeInput; +use graphene_std::wasm_application_io::WasmApplicationIo; +use interpreted_executor::dynamic_executor::DynamicExecutor; use interpreted_executor::util::wrap_network_in_scope; use once_cell::sync::Lazy; use spin::Mutex; @@ -41,9 +29,6 @@ pub struct NodeRuntime { node_graph_errors: GraphErrors, - /// Which node is inspected and which monitor node is used (if any) for the current execution - inspect_state: Option, - /// Mapping of the fully-qualified node paths to their preprocessor substitutions. substitutions: HashMap, @@ -61,10 +46,10 @@ pub enum GraphRuntimeRequest { // 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), + // 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)>), + IntrospectionRequest(HashSet), } #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -87,6 +72,9 @@ impl NodeGraphRuntimeSender { fn send_evaluation_response(&self, response: EvaluationResponse) { self.0.send(NodeGraphUpdate::EvaluationResponse(response)).expect("Failed to send evaluation response") } + fn send_introspection_response(&self, response: IntrospectionResponse) { + self.0.send(NodeGraphUpdate::IntrospectionResponse(response)).expect("Failed to send introspection response") + } } pub static NODE_RUNTIME: Lazy>> = Lazy::new(|| Mutex::new(None)); @@ -103,119 +91,103 @@ impl NodeRuntime { node_graph_errors: Vec::new(), substitutions: preprocessor::generate_node_substitutions(), - thumbnail_render_tagged_values: HashSet::new(), - inspect_state: None, } } pub async fn run(&mut self) { if self.application_io.is_none() { - #[cfg(not(test))] + // #[cfg(not(test))] self.application_io = Some(Arc::new(WasmApplicationIo::new().await)); - #[cfg(test)] - self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await)); + // #[cfg(test)] + // self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await)); } - // 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(); - + // TODO: This deduplication of messages will probably cause issues + let mut compilation = None; + let mut evaluation = None; + let mut introspection = None; for request in self.receiver.try_iter() { match request { - GraphRuntimeRequest::CompilationRequest(CompilationRequest { - mut network, - font_cache, - editor_metadata, - }) => { + GraphRuntimeRequest::CompilationRequest(_) => compilation = Some(request), + GraphRuntimeRequest::EvaluationRequest(_) => evaluation = Some(request), + GraphRuntimeRequest::IntrospectionRequest(_) => introspection = Some(request), + } + } + let requests = [compilation, evaluation, introspection].into_iter().flatten(); + + for request in requests { + match request { + GraphRuntimeRequest::CompilationRequest(CompilationRequest { 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.node_graph_errors.clear(); - let result = self.update_network(network).await; + let result = self.update_network(network, font_cache, editor_metadata).await; self.sender.send_compilation_response(CompilationResponse { result, node_graph_errors: self.node_graph_errors.clone(), }); } + // Inputs to monitor is sent from the editor, and represents a list of input connectors to track the data through + // During the execution. If the value is None, then the node was not evaluated, which can occur due to caching GraphRuntimeRequest::EvaluationRequest(EvaluationRequest { evaluation_id, context, - inputs_to_monitor, - // custom_node_to_evaluate + 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; + // for (protonode_input, introspect_mode) in &inputs_to_monitor { + // self.executor.set_introspect(*protonode_input, *introspect_mode) + // } + let result = self.executor.evaluate_from_node(context, node_to_evaluate).await; - let result = self.execute_network(render_config).await; - - 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; + self.sender.send_evaluation_response(EvaluationResponse { evaluation_id, result }); + } + // GraphRuntimeRequest::ThumbnailRenderRequest(_) => {} + GraphRuntimeRequest::IntrospectionRequest(inputs) => { + let mut introspected_inputs = Vec::new(); + for protonode_input in inputs { + let introspected_data = match self.executor.introspect(protonode_input, IntrospectMode::Data) { + Ok(introspected_data) => introspected_data, + Err(e) => { + log::error!("Could not introspect input: {:?}, error: {:?}", protonode_input, e); + continue; + } }; - introspected_inputs.push((protonode_input, mode, introspected_data)); + introspected_inputs.push((protonode_input, IntrospectMode::Data, introspected_data)); } - self.sender.send_evaluation_response(EvaluationResponse { - evaluation_id, - result, - transform, - 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); + self.sender.send_introspection_response(IntrospectionResponse(introspected_inputs)); } } } } - async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { + async fn update_network(&mut self, mut graph: NodeNetwork, font_cache: Arc, editor_metadata: EditorMetadata) -> 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()); + let mut scoped_network = wrap_network_in_scope(graph, font_cache, editor_metadata, self.application_io.as_ref().unwrap().clone()); // We assume only one output assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled"); // 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() { + let (proto_network, protonode_caller_for_values, protonode_caller_for_nodes) = match scoped_network.flatten() { Ok(network) => network, Err(e) => { log::error!("Error compiling network: {e:?}"); - return; + return Err(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, + protonode_caller_for_values, + protonode_caller_for_nodes, types_to_add, types_to_remove, }) @@ -225,23 +197,8 @@ impl NodeRuntime { Err(format!("{e:?}")) } }; - } - - async fn execute_network(&mut self, render_config: RenderConfig) -> Result { - use graph_craft::graphene_compiler::Executor; - - let result = match self.executor.input_type() { - Some(t) if t == concrete!(RenderConfig) => (&self.executor).execute(render_config).await.map_err(|e| e.to_string()), - Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()), - Some(t) => Err(format!("Invalid input type {t:?}")), - _ => Err(format!("No input type:\n{:?}", self.node_graph_errors)), - }; - let result = match result { - Ok(value) => value, - Err(e) => return Err(e), - }; - - Ok(result) + // log::debug!("result: {:?}", result); + result } } diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index eaedc3a6d..96a49d72b 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -613,16 +613,25 @@
{#each $nodeGraph.wires.values() as map} - {#each map.values() as { pathString, dataType, thick, dashed }} + {#each map.values() as { pathString, dataType, thick, dashed, center, monitorSni }} {#if !thick} {/if} + + + {#if center && monitorSni} + + + {@html $nodeGraph.thumbnails.get(monitorSni)} + + {/if} {/each} {/each} {#if $nodeGraph.wirePathInProgress} @@ -888,7 +897,7 @@ height: 100%; overflow: visible; - path { + .wire-path { fill: none; stroke: var(--data-color-dim); stroke-width: var(--data-line-width); diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index c7979aa67..9295b642c 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -321,6 +321,9 @@ export class WirePath { readonly dataType!: FrontendGraphDataType; readonly thick!: boolean; readonly dashed!: boolean; + @TupleToVec2 + readonly center!: XY | undefined; + readonly inputSni!: bigint; } export class WireUpdate { @@ -1683,7 +1686,7 @@ export const messageMakers: Record = { UpdateNodeGraphTransform, UpdateNodeGraphControlBarLayout, UpdateNodeGraphSelection, - UpdateThumbnails: UpdateThumbnail, + UpdateThumbnails, UpdateOpenDocumentsList, UpdatePropertyPanelSectionsLayout, UpdateSpreadsheetLayout, diff --git a/frontend/src/state-providers/node-graph.ts b/frontend/src/state-providers/node-graph.ts index e0c8128bb..e0e214ad3 100644 --- a/frontend/src/state-providers/node-graph.ts +++ b/frontend/src/state-providers/node-graph.ts @@ -118,6 +118,7 @@ export function createNodeGraphState(editor: Editor) { }); }); editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => { + // console.log(updateNodeGraphNodes); update((state) => { state.nodes.clear(); updateNodeGraphNodes.nodes.forEach((node) => { @@ -168,14 +169,16 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); - editor.subscriptions.subscribeJsMessage(UpdateThumbnails, (updateThumbnail) => { + editor.subscriptions.subscribeJsMessage(UpdateThumbnails, (updateThumbnails) => { + // console.log("thumbnail update: ", updateThumbnails); update((state) => { - for (const [id, value] of updateThumbnail.add) { + for (const [id, value] of updateThumbnails.add) { state.thumbnails.set(id, value); } - for (const id of updateThumbnail.clear) { + for (const id of updateThumbnails.clear) { state.thumbnails.set(id, ""); } + // console.log("thumbnails: ", state.thumbnails); return state; }); }); diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index c8056e4ef..a4e539df4 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -15,8 +15,8 @@ use editor::messages::portfolio::document::utility_types::network_interface::Imp use editor::messages::portfolio::utility_types::Platform; use editor::messages::prelude::*; use editor::messages::tool::tool_messages::tool_prelude::WidgetId; -use graph_craft::document::NodeId; use graphene_std::raster::color::Color; +use graphene_std::uuid::NodeId; use serde::Serialize; use serde_wasm_bindgen::{self, from_value}; use std::cell::RefCell; diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index d3558fc22..a1253d859 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -486,6 +486,21 @@ impl Subpath { None } + pub fn center(&self) -> Option { + let len = self.manipulator_groups.len(); + if len == 0 { + return None; + } else if len % 2 == 1 { + return Some(self.manipulator_groups[len / 2].anchor); + } else { + let p0 = self.manipulator_groups[len / 2 - 1].anchor; + let p1 = self.manipulator_groups[len / 2 - 1].out_handle; + let p2 = self.manipulator_groups[len / 2].in_handle; + let p3 = self.manipulator_groups[len / 2].anchor; + Some(0.125 * p0 + 0.375 * p1.unwrap_or(p0) + 0.375 * p2.unwrap_or(p3) + 0.125 * p3) + } + } + /// Returns the necessary information to create a round join with the provided center. /// The returned items correspond to: /// - The `out_handle` for the last manipulator group of `self` diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index 57bb10a79..8a72dbaa9 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -123,6 +123,21 @@ pub fn downcast<'a, V: StaticType + 'a>(i: Box + 'a>) -> Result(i: Box + 'a>) -> Result, Box + 'a>> { + let type_id = DynAny::type_id(i.as_ref()); + if type_id == core::any::TypeId::of::<::Static>() { + // SAFETY: caller guarantees that T is the correct type + let ptr = Box::into_raw(i) as *mut V; + Ok(unsafe { Box::from_raw(ptr) }) + } else { + if type_id == core::any::TypeId::of::<&dyn DynAny<'static>>() { + panic!("downcast error: type_id == core::any::TypeId::of::>()"); + } + Err(i) + } +} + pub unsafe trait StaticType { type Static: 'static + ?Sized; fn type_id(&self) -> core::any::TypeId { diff --git a/node-graph/gapplication-io/src/lib.rs b/node-graph/gapplication-io/src/lib.rs index d0fd84c6d..e069b8b73 100644 --- a/node-graph/gapplication-io/src/lib.rs +++ b/node-graph/gapplication-io/src/lib.rs @@ -1,6 +1,5 @@ use dyn_any::{DynAny, StaticType, StaticTypeSized}; use glam::{DAffine2, UVec2}; -use graphene_core::text::FontCache; use graphene_core::transform::Footprint; use graphene_core::vector::style::ViewMode; use std::fmt::Debug; @@ -236,69 +235,23 @@ pub struct RenderConfig { pub for_export: bool, } -struct Logger; +#[derive(Default, Clone, Debug)] +pub struct ApplicationIoValue(pub Option>); -impl NodeGraphUpdateSender for Logger { - fn send(&self, message: NodeGraphUpdateMessage) { - log::warn!("dispatching message with fallback node graph update sender {:?}", message); - } +unsafe impl StaticType for ApplicationIoValue { + type Static = ApplicationIoValue; } -struct DummyPreferences; +impl Eq for ApplicationIoValue {} -impl GetEditorPreferences for DummyPreferences { - fn use_vello(&self) -> bool { - false - } -} - -pub struct EditorApi { - /// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`]. - pub font_cache: FontCache, - /// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web). - pub application_io: Option>, - pub node_graph_message_sender: Box, - /// Editor preferences made available to the graph through the [`WasmEditorApi`]. - pub editor_preferences: Box, -} - -impl Eq for EditorApi {} - -impl Default for EditorApi { - fn default() -> Self { - Self { - font_cache: FontCache::default(), - application_io: None, - node_graph_message_sender: Box::new(Logger), - editor_preferences: Box::new(DummyPreferences), - } - } -} - -impl Hash for EditorApi { +impl Hash for ApplicationIoValue { fn hash(&self, state: &mut H) { - self.font_cache.hash(state); - self.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state); - (self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state); - (self.editor_preferences.as_ref() as *const dyn GetEditorPreferences).hash(state); + self.0.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state); } } -impl PartialEq for EditorApi { +impl PartialEq for ApplicationIoValue { fn eq(&self, other: &Self) -> bool { - self.font_cache == other.font_cache - && self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) - && std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _) - && std::ptr::eq(self.editor_preferences.as_ref() as *const _, other.editor_preferences.as_ref() as *const _) + self.0.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.0.as_ref().map_or(0, |io| addr_of!(io) as usize) } } - -impl Debug for EditorApi { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EditorApi").field("font_cache", &self.font_cache).finish() - } -} - -unsafe impl StaticType for EditorApi { - type Static = EditorApi; -} diff --git a/node-graph/gcore/src/animation.rs b/node-graph/gcore/src/animation.rs index fe992397b..b587ae92d 100644 --- a/node-graph/gcore/src/animation.rs +++ b/node-graph/gcore/src/animation.rs @@ -1,4 +1,4 @@ -use crate::{Ctx, ExtractAnimationTime, ExtractTime}; +use crate::{Ctx, ExtractAnimationTime, ExtractRealTime}; const DAY: f64 = 1000. * 3600. * 24.; @@ -21,8 +21,8 @@ pub enum AnimationTimeMode { } #[node_macro::node(category("Animation"))] -fn real_time(ctx: impl Ctx + ExtractTime, _primary: (), mode: RealTimeMode) -> f64 { - let time = ctx.try_time().unwrap_or_default(); +fn real_time(ctx: impl Ctx + ExtractRealTime, _primary: (), mode: RealTimeMode) -> f64 { + let time = ctx.try_real_time().unwrap_or_default(); // TODO: Implement proper conversion using and existing time implementation match mode { RealTimeMode::Utc => time, diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 502f91bb8..01ccd0ed1 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -1,13 +1,13 @@ +use glam::{DAffine2, UVec2}; + use crate::transform::Footprint; use std::any::Any; -use std::borrow::Borrow; use std::panic::Location; use std::sync::Arc; pub trait Ctx: Clone + Send {} pub trait ExtractFootprint { - #[track_caller] fn try_footprint(&self) -> Option<&Footprint>; #[track_caller] fn footprint(&self) -> &Footprint { @@ -18,8 +18,12 @@ pub trait ExtractFootprint { } } -pub trait ExtractTime { - fn try_time(&self) -> Option; +pub trait ExtractDownstreamTransform { + fn try_downstream_transform(&self) -> Option<&DAffine2>; +} + +pub trait ExtractRealTime { + fn try_real_time(&self) -> Option; } pub trait ExtractAnimationTime { @@ -42,9 +46,32 @@ pub trait CloneVarArgs: ExtractVarArgs { fn arc_clone(&self) -> Option>; } -pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs {} +pub trait ExtractAll: ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs {} -impl ExtractAll for T {} +impl ExtractAll for T {} + +#[derive(Debug, Clone, PartialEq)] +pub enum ContextDependency { + ExtractFootprint, + // Can be used by cull nodes to check if the final output would be outside the footprint viewport + ExtractDownstreamTransform, + ExtractRealTime, + ExtractAnimationTime, + ExtractIndex, + ExtractVarArgs, +} + +pub fn all_context_dependencies() -> Vec { + vec![ + ContextDependency::ExtractFootprint, + // Can be used by cull nodes to check if the final output would be outside the footprint viewport + ContextDependency::ExtractDownstreamTransform, + ContextDependency::ExtractRealTime, + ContextDependency::ExtractAnimationTime, + ContextDependency::ExtractIndex, + ContextDependency::ExtractVarArgs, + ] +} #[derive(Debug, Clone, PartialEq, Eq)] pub enum VarArgsResult { @@ -72,17 +99,30 @@ impl ExtractFootprint for Option { fn try_footprint(&self) -> Option<&Footprint> { self.as_ref().and_then(|x| x.try_footprint()) } - #[track_caller] - fn footprint(&self) -> &Footprint { - self.try_footprint().unwrap_or_else(|| { - log::warn!("trying to extract footprint from context None {} ", Location::caller()); - &Footprint::DEFAULT - }) +} + +impl ExtractDownstreamTransform for () { + fn try_downstream_transform(&self) -> Option<&DAffine2> { + log::error!("tried to extract downstream transform form (), {}", Location::caller()); + None } } -impl ExtractTime for Option { - fn try_time(&self) -> Option { - self.as_ref().and_then(|x| x.try_time()) + +impl ExtractDownstreamTransform for &T { + fn try_downstream_transform(&self) -> Option<&DAffine2> { + (*self).try_downstream_transform() + } +} + +impl ExtractDownstreamTransform for Option { + fn try_downstream_transform(&self) -> Option<&DAffine2> { + self.as_ref().and_then(|x| x.try_downstream_transform()) + } +} + +impl ExtractRealTime for Option { + fn try_real_time(&self) -> Option { + self.as_ref().and_then(|x| x.try_real_time()) } } impl ExtractAnimationTime for Option { @@ -111,9 +151,16 @@ impl ExtractFootprint for Arc { (**self).try_footprint() } } -impl ExtractTime for Arc { - fn try_time(&self) -> Option { - (**self).try_time() + +impl ExtractDownstreamTransform for Arc { + fn try_downstream_transform(&self) -> Option<&DAffine2> { + (**self).try_downstream_transform() + } +} + +impl ExtractRealTime for Arc { + fn try_real_time(&self) -> Option { + (**self).try_real_time() } } impl ExtractAnimationTime for Arc { @@ -156,43 +203,22 @@ impl CloneVarArgs for Arc { } } -impl Ctx for ContextImpl<'_> {} impl Ctx for Arc {} -impl ExtractFootprint for ContextImpl<'_> { - fn try_footprint(&self) -> Option<&Footprint> { - self.footprint - } -} -impl ExtractTime for ContextImpl<'_> { - fn try_time(&self) -> Option { - self.time - } -} -impl ExtractIndex for ContextImpl<'_> { - fn try_index(&self) -> Option> { - self.index.clone() - } -} -impl ExtractVarArgs for ContextImpl<'_> { - fn vararg(&self, index: usize) -> Result, VarArgsResult> { - let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; - inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied() - } - - fn varargs_len(&self) -> Result { - let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; - Ok(inner.len()) - } -} - impl ExtractFootprint for OwnedContextImpl { fn try_footprint(&self) -> Option<&Footprint> { self.footprint.as_ref() } } -impl ExtractTime for OwnedContextImpl { - fn try_time(&self) -> Option { + +impl ExtractDownstreamTransform for OwnedContextImpl { + fn try_downstream_transform(&self) -> Option<&DAffine2> { + self.downstream_transform.as_ref() + } +} + +impl ExtractRealTime for OwnedContextImpl { + fn try_real_time(&self) -> Option { self.real_time } } @@ -234,20 +260,26 @@ impl CloneVarArgs for Arc { } } -// Lifetime isnt necessary? pub type Context<'a> = Option>; type DynRef<'a> = &'a (dyn Any + Send + Sync); type DynBox = Box; #[derive(dyn_any::DynAny)] pub struct OwnedContextImpl { + // The footprint represents the document to viewport render metadata footprint: Option, - varargs: Option>, - parent: Option>, - // This could be converted into a single enum to save extra bytes - index: Option>, + // The transform node does not modify the document to viewport, it instead modifies this, + // which can be used to transform the evaluated data from the node and check if it is within the + // document to viewport transform. + downstream_transform: Option, + index: Option, real_time: Option, animation_time: Option, + + // varargs: Option<(Vec, Arc<[DynBox]>)>, + varargs: Option>, + + parent: Option>, } impl std::fmt::Debug for OwnedContextImpl { @@ -285,8 +317,9 @@ impl OwnedContextImpl { #[track_caller] pub fn from(value: T) -> Self { let footprint = value.try_footprint().copied(); + let downstream_transform = value.try_downstream_transform().copied(); let index = value.try_index(); - let time = value.try_time(); + let time = value.try_real_time(); let frame_time = value.try_animation_time(); let parent = match value.varargs_len() { Ok(x) if x > 0 => value.arc_clone(), @@ -294,21 +327,40 @@ impl OwnedContextImpl { }; OwnedContextImpl { footprint, - varargs: None, - parent, + downstream_transform, index, real_time: time, animation_time: frame_time, + varargs: None, + parent, } } + pub const fn empty() -> Self { OwnedContextImpl { footprint: None, - varargs: None, - parent: None, + downstream_transform: None, index: None, real_time: None, animation_time: None, + varargs: None, + parent: None, + } + } + + pub fn nullify(&mut self, nullify: &Vec) { + for context_dependency in nullify { + match context_dependency { + ContextDependency::ExtractFootprint => self.footprint = None, + ContextDependency::ExtractDownstreamTransform => self.downstream_transform = None, + ContextDependency::ExtractRealTime => self.real_time = None, + ContextDependency::ExtractAnimationTime => self.animation_time = None, + ContextDependency::ExtractIndex => self.index = None, + ContextDependency::ExtractVarArgs => { + self.varargs = None; + self.parent = None + } + } } } } @@ -317,10 +369,31 @@ impl OwnedContextImpl { pub fn set_footprint(&mut self, footprint: Footprint) { self.footprint = Some(footprint); } + pub fn set_downstream_transform(&mut self, transform: DAffine2) { + self.downstream_transform = Some(transform); + } + pub fn try_apply_downstream_transform(&mut self, transform: DAffine2) { + if let Some(downstream_transform) = self.downstream_transform { + self.downstream_transform = Some(downstream_transform * transform); + } + } + pub fn set_real_time(&mut self, time: f64) { + self.real_time = Some(time); + } + pub fn set_animation_time(&mut self, animation_time: f64) { + self.animation_time = Some(animation_time); + } + pub fn set_index(&mut self, index: usize) { + self.index = Some(index); + } pub fn with_footprint(mut self, footprint: Footprint) -> Self { self.footprint = Some(footprint); self } + pub fn with_downstream_transform(mut self, downstream_transform: DAffine2) -> Self { + self.downstream_transform = Some(downstream_transform); + self + } pub fn with_real_time(mut self, time: f64) -> Self { self.real_time = Some(time); self @@ -329,11 +402,6 @@ impl OwnedContextImpl { self.animation_time = Some(animation_time); self } - pub fn with_vararg(mut self, value: Box) -> Self { - assert!(self.varargs.is_none_or(|value| value.is_empty())); - self.varargs = Some(Arc::new([value])); - self - } pub fn with_index(mut self, index: usize) -> Self { if let Some(current_index) = &mut self.index { current_index.push(index); @@ -345,31 +413,193 @@ impl OwnedContextImpl { pub fn into_context(self) -> Option> { Some(Arc::new(self)) } + pub fn add_vararg(mut self, _variable_name: String, value: Box) -> Self { + assert!(self.varargs.is_none_or(|value| value.is_empty())); + // self.varargs = Some((vec![variable_name], Arc::new([value]))); + self.varargs = Some(Arc::new([value])); + + self + } + pub fn set_varargs(&mut self, var_args: (Vec, Arc<[DynBox]>)) { + self.varargs = Some(var_args.1) + } + pub fn with_vararg(mut self, var_args: (impl Into, DynBox)) -> Self { + self.varargs = Some(Arc::new([var_args.1])); + self + } pub fn erase_parent(mut self) -> Self { self.parent = None; self } } -#[derive(Default, Clone, dyn_any::DynAny)] -pub struct ContextImpl<'a> { - pub(crate) footprint: Option<&'a Footprint>, - varargs: Option<&'a [DynRef<'a>]>, - // This could be converted into a single enum to save extra bytes - index: Option>, - time: Option, +// #[derive(Default, Clone, Copy, dyn_any::DynAny)] +// pub struct ContextImpl<'a> { +// pub(crate) footprint: Option<&'a Footprint>, +// varargs: Option<&'a [DynRef<'a>]>, +// // This could be converted into a single enum to save extra bytes +// index: Option, +// time: Option, +// } + +// impl<'a> ContextImpl<'a> { +// pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f> +// where +// 'a: 'f, +// { +// ContextImpl { +// footprint: Some(new_footprint), +// varargs: varargs.map(|x| x.borrow()), +// ..*self +// } +// } +// } + +#[node_macro::node(category("Context Getter"))] +fn get_footprint(ctx: impl Ctx + ExtractFootprint) -> Option { + ctx.try_footprint().copied() } -impl<'a> ContextImpl<'a> { - pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f> - where - 'a: 'f, - { - ContextImpl { - footprint: Some(new_footprint), - varargs: varargs.map(|x| x.borrow()), - index: self.index.clone(), - ..*self - } - } +#[node_macro::node(category("Context Getter"))] +fn get_document_to_viewport(ctx: impl Ctx + ExtractFootprint) -> Option { + ctx.try_footprint().map(|footprint| footprint.transform.clone()) +} + +#[node_macro::node(category("Context Getter"))] +fn get_resolution(ctx: impl Ctx + ExtractFootprint) -> Option { + ctx.try_footprint().map(|footprint| footprint.resolution.clone()) +} + +#[node_macro::node(category("Context Getter"))] +fn get_downstream_transform(ctx: impl Ctx + ExtractDownstreamTransform) -> Option { + ctx.try_downstream_transform().copied() +} + +#[node_macro::node(category("Context Getter"))] +fn get_real_time(ctx: impl Ctx + ExtractRealTime) -> Option { + ctx.try_real_time() +} + +#[node_macro::node(category("Context Getter"))] +fn get_animation_time(ctx: impl Ctx + ExtractAnimationTime) -> Option { + ctx.try_animation_time() +} + +#[node_macro::node(category("Context Getter"))] +fn get_index(ctx: impl Ctx + ExtractIndex) -> Option { + ctx.try_index().map(|index| index as u32) +} + +// #[node_macro::node(category("Loop"))] +// async fn loop_node( +// ctx: impl Ctx + CloneVarArgs + ExtractAll, +// #[implementations( +// Context -> Option, +// )] +// return_if_some: impl Node, Output = Option>, +// #[implementations( +// Context -> (), +// )] +// run_if_none: impl Node, Output = ()>, +// ) -> T { +// let mut context = OwnedContextImpl::from(ctx.clone()); +// context.arc_mutex = Some(Arc::new(Mutex::new(None))); +// loop { +// if let Some(return_value) = return_if_some.eval(context.clone().into_context()).await { +// return return_value; +// } +// run_if_none.eval(context.clone().into_context()).await; +// let Some(context_after_loop) = context.arc_mutex.unwrap().lock().unwrap().take() else { +// log::error!("Loop context was not set, breaking loop to avoid infinite loop"); +// return T::default(); +// }; +// context = context_after_loop; +// context.arc_mutex = Some(Arc::new(Mutex::new(None))); +// } +// } + +// #[node_macro::node(category("Loop"))] +// async fn update_loop_node_context(ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync) -> () { +// let mut context = OwnedContextImpl::from(ctx.clone()); +// let context_after_loop = OwnedContextImpl::from(ctx.clone()); +// if let Some(arc_mutex) = context.arc_mutex.as_ref() { +// *arc_mutex.lock().unwrap() = Some(context_after_loop); +// } +// } + +#[node_macro::node(category("Loop"))] +async fn set_index( + ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync, + #[expose] + #[implementations( + Context -> u32, + Context -> (), + )] + input: impl Node, Output = T>, + number: u32, +) -> T { + let mut new_context = OwnedContextImpl::from(ctx); + new_context.index = Some(number.try_into().unwrap()); + input.eval(new_context.into_context()).await +} + +// #[node_macro::node(category("Loop"))] +// fn create_arc_mutex(_ctx: impl Ctx) -> Arc>> { +// Arc::new(Mutex::new(0)) +// } + +// #[node_macro::node(category("Loop"))] +// fn get_arc_mutex(ctx: impl Ctx + ExtractArcMutex) -> Option>>> { +// ctx.try_arc_mutex() +// } + +// #[node_macro::node(category("Loop"))] +// async fn set_arc_mutex( +// ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync, +// // Auto generate for each tagged value type +// #[expose] +// #[implementations( +// Context -> u32, +// )] +// input: impl Node, Output = T>, +// arc_mutex: Arc>>, +// ) -> T { +// let mut new_context = OwnedContextImpl::from(ctx); +// new_context.arc_mutex = Some(arc_mutex); +// input.eval(new_context.into_context()).await +// } + +// // TODO: Discard node + return () for loop if none branch +// #[node_macro::node(category("Loop"))] +// fn set_arc_mutex_value(_ctx: impl Ctx, #[implementations(Arc>)] arc_mutex: Arc>, #[implementations(u32)] value: T) -> () { +// let mut guard = arc_mutex.lock().unwrap(); // lock the mutex +// *guard = value; +// } + +// #[node_macro::node(category("Loop"))] +// fn get_context(ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync) -> OwnedContextImpl { +// OwnedContextImpl::from(ctx) +// } + +// #[node_macro::node(category("Loop"))] +// fn discard(_ctx: impl Ctx, _input: u32) -> () {} + +#[node_macro::node(category("Debug"))] +fn is_none(_: impl Ctx, #[implementations(Option, Option, Option, Option, Option)] input: Option) -> bool { + input.is_none() +} + +// #[node_macro::node(category("Debug"))] +// fn unwrap(_: impl Ctx, #[implementations(Option, Option, Option, Option, Option, Option>)] input: Option) -> T { +// input.unwrap() +// } + +#[node_macro::node(category("Debug"))] +fn to_option(_: impl Ctx, boolean: bool, #[implementations(u32)] input: T) -> Option { + boolean.then(|| input) +} + +#[node_macro::node(category("Debug"))] +fn to_usize(_: impl Ctx, u32: u32) -> usize { + u32.try_into().unwrap() } diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 53b6b02a6..80ef4af4f 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -62,7 +62,7 @@ pub trait Node<'i, Input> { /// 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> { + fn introspect(&self, _introspect_mode: IntrospectMode) -> Option> { log::warn!("Node::introspect not implemented for {}", std::any::type_name::()); None } diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index 73ba5077b..2f59a5e70 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -7,6 +7,88 @@ use std::ops::Deref; use std::sync::Arc; use std::sync::Mutex; +/// Caches the output of a given Node and acts as a proxy +#[derive(Default)] +pub struct MonitorMemoNode { + // Introspection cache, uses the hash of the nullified context with default var args + // cache: Arc>>>, + // Return value cache, + cache: Arc)>>>, + node: CachedNode, + changed_since_last_eval: Arc>, +} +impl<'i, I: Hash + 'i + std::fmt::Debug, T: 'static + Clone + Send + Sync, CachedNode: 'i> Node<'i, I> for MonitorMemoNode +where + CachedNode: for<'any_input> Node<'any_input, I>, + for<'a> >::Output: Future + WasmNotSend, +{ + // TODO: This should return a reference to the cached cached_value + // but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty accurate xD + type Output = DynFuture<'i, T>; + // fn eval(&'i self, input: I) -> Self::Output { + // let mut hasher = DefaultHasher::new(); + // input.hash(&mut hasher); + // let hash = hasher.finish(); + + // if let Some(data) = self.cache.lock().unwrap().get(&hash) { + // let cloned_data = (**data).clone(); + // Box::pin(async move { cloned_data }) + // } else { + // let fut = self.node.eval(input); + // let cache = self.cache.clone(); + // Box::pin(async move { + // let value = fut.await; + // cache.lock().unwrap().insert(hash, Arc::new(value.clone())); + // value + // }) + // } + // } + + // fn introspect(&self, _introspect_mode: IntrospectMode) -> Option> { + // let mut hasher = DefaultHasher::new(); + // OwnedContextImpl::default().into_context().hash(&mut hasher); + // let hash = hasher.finish(); + // self.cache.lock().unwrap().get(&hash).map(|data| (*data).clone() as Arc) + // } + fn eval(&'i self, input: I) -> Self::Output { + let mut hasher = DefaultHasher::new(); + input.hash(&mut hasher); + let hash = hasher.finish(); + + if let Some(data) = self.cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) { + let cloned_data = (*data).clone(); + Box::pin(async move { cloned_data }) + } else { + let fut = self.node.eval(input); + let cache = self.cache.clone(); + *self.changed_since_last_eval.lock().unwrap() = true; + Box::pin(async move { + let value = fut.await; + *cache.lock().unwrap() = Some((hash, Arc::new(value.clone()))); + value + }) + } + } + fn introspect(&self, _introspect_mode: IntrospectMode) -> Option> { + if *self.changed_since_last_eval.lock().unwrap() { + *self.changed_since_last_eval.lock().unwrap() = false; + Some(self.cache.lock().unwrap().as_ref().expect("Cached data should always be evaluated before introspection").1.clone() as Arc) + } else { + None + } + } +} + +impl MonitorMemoNode { + pub fn new(node: CachedNode) -> MonitorMemoNode { + MonitorMemoNode { + cache: Default::default(), + node, + changed_since_last_eval: Arc::new(Mutex::new(true)), + } + } +} + /// Caches the output of a given Node and acts as a proxy #[derive(Default)] pub struct MemoNode { @@ -107,7 +189,7 @@ pub mod impure_memo { pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode"); } -#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub enum IntrospectMode { Input, Data, @@ -117,8 +199,8 @@ pub enum IntrospectMode { #[derive(Default)] pub struct MonitorNode { #[allow(clippy::type_complexity)] - input: Arc>>>, - output: 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>, @@ -137,12 +219,12 @@ where 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)); + *self.input.lock().unwrap() = Some(Arc::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())); + *self.output.lock().unwrap() = Some(Arc::new(output.clone())); *introspect_output = false; } output @@ -150,10 +232,10 @@ where } // 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> { + 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), + IntrospectMode::Input => self.input.lock().unwrap().take().map(|input| input as Arc), + IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Arc), } } diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 43c7fbe6d..f0e16bea3 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -109,6 +109,8 @@ pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::ne pub static NODE_METADATA: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); +pub static NODE_CONTEXT_DEPENDENCY: LazyLock>>> = LazyLock::new(|| Mutex::new(HashMap::new())); + #[cfg(not(target_arch = "wasm32"))] pub type DynFuture<'n, T> = Pin + 'n + Send>>; #[cfg(target_arch = "wasm32")] @@ -132,7 +134,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>; +pub type CacheConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>; #[derive(Clone)] pub struct NodeContainer { @@ -277,17 +279,24 @@ where type Output = FutureAny<'input>; #[inline] fn eval(&'input self, input: Any<'input>) -> Self::Output { - let node_name = std::any::type_name::(); let output = |input| { let result = self.node.eval(input); async move { Box::new(result.await) as Any<'input> } }; match dyn_any::downcast(input) { Ok(input) => Box::pin(output(*input)), - Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name), + Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, std::any::type_name::()), } } + fn introspect(&self, introspect_mode: crate::IntrospectMode) -> Option> { + self.node.introspect(introspect_mode) + } + + fn set_introspect(&self, introspect_mode: crate::IntrospectMode) { + self.node.set_introspect(introspect_mode); + } + fn reset(&self) { self.node.reset(); } diff --git a/node-graph/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index 24ef44fa6..b6488c573 100644 --- a/node-graph/gcore/src/structural.rs +++ b/node-graph/gcore/src/structural.rs @@ -1,4 +1,4 @@ -use crate::registry::Node; +use crate::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 6f252dcf6..3bc86ef41 100644 --- a/node-graph/gcore/src/uuid.rs +++ b/node-graph/gcore/src/uuid.rs @@ -91,5 +91,5 @@ 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]>; +// Path to the protonode in the wrapped network (document network prefixed with NodeId(0)) +pub type ProtonodePath = Vec; diff --git a/node-graph/gcore/src/vector/algorithms/instance.rs b/node-graph/gcore/src/vector/algorithms/instance.rs index 2dd23150d..394fbc4cf 100644 --- a/node-graph/gcore/src/vector/algorithms/instance.rs +++ b/node-graph/gcore/src/vector/algorithms/instance.rs @@ -22,7 +22,7 @@ async fn instance_on_points + Default + Send + Clone + ' let mut iteration = async |index, point| { let transformed_point = transform.transform_point2(point); - let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point)); + let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(("Transformed point", Box::new(transformed_point))); let generated_instance = instance.eval(new_ctx.into_context()).await; for mut instanced in generated_instance.instance_iter() { diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 88b36a6ed..6f6e6f6d8 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -1,16 +1,17 @@ pub mod value; use crate::document::value::TaggedValue; -use crate::proto::{ConstructionArgs, NodeConstructionArgs, OriginalLocation, ProtoNode}; +use crate::proto::{ConstructionArgs, NodeConstructionArgs, NodeValueArgs, ProtoNetwork, ProtoNode, UpstreamInputMetadata}; use dyn_any::DynAny; use glam::IVec2; use graphene_core::memo::MemoHashGuard; +use graphene_core::registry::NODE_CONTEXT_DEPENDENCY; pub use graphene_core::uuid::generate_uuid; 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}; /// Utility function for providing a default boolean value to serde. @@ -509,94 +510,170 @@ impl NodeNetwork { /// Functions for compiling the network impl NodeNetwork { - // Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation + // Returns a topologically sorted vec of vec of protonodes, as well as metadata extracted during compilation + // The first index represents the greatest distance to the export // 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> { + pub fn flatten( + &mut self, + ) -> Result< + ( + ProtoNetwork, + Vec<(Vec, CompiledProtonodeInput)>, + Vec<(Vec, 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(); - // This function creates a flattened network with populated original location fields but unmapped inputs + // This function creates a topologically 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), - ); + let mut protonode_indices = HashMap::new(); + self.traverse_input(&mut protonetwork, &mut HashMap::new(), &mut protonode_indices, AbsoluteInputConnector::traversal_start(), None); - let mut generated_snis = HashSet::new(); + // If a node with the same sni is reached, then its original location metadata must be added to the one at the higher vec index + // The index will always be a ProtonodeEntry::Protonode + let mut generated_snis_to_index = HashMap::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" - ); - } - } - - 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; + for protonode_index in 0..protonetwork.len() { + let ProtonodeEntry::Protonode(protonode) = protonetwork.get_mut(protonode_index).unwrap() else { + panic!("No protonode can be deduplicated during flattening"); + }; + // Generate context dependencies. If None, then it is a value node and does not require nullification + let mut protonode_context_dependencies = None; + if let ConstructionArgs::Nodes(NodeConstructionArgs { inputs, context_dependencies, .. }) = &mut protonode.construction_args { + for upstream_metadata in inputs.iter() { + let Some(upstream_metadata) = upstream_metadata else { + panic!("All inputs should be when the upstream SNI was generated"); + }; + for upstream_dependency in upstream_metadata.context_dependencies.iter().flatten() { + if !context_dependencies.contains(upstream_dependency) { + context_dependencies.push(upstream_dependency.clone()); + } } - // TODO: Implement for extract - _ => unreachable!(), + } + // The context_dependencies are now the union of all inputs and the dependencies of the protonode. Set the dependencies of each input to the difference, which represents the data to nullify + for upstream_metadata in inputs.iter_mut() { + let Some(upstream_metadata) = upstream_metadata else { + panic!("All inputs should be when the upstream SNI was generated"); + }; + match upstream_metadata.context_dependencies.as_ref() { + Some(upstream_dependencies) => { + upstream_metadata.context_dependencies = Some( + context_dependencies + .iter() + .filter(|protonode_dependency| !upstream_dependencies.contains(protonode_dependency)) + .cloned() + .collect::>(), + ) + } + // If none then the upstream node is a Value node, so do not nullify the context + None => upstream_metadata.context_dependencies = Some(Vec::new()), + } + } + protonode_context_dependencies = Some(context_dependencies.clone()); + } + + protonode.generate_stable_node_id(); + let current_stable_node_id = protonode.stable_node_id; + + // If the stable node id is the same as a previous node, then deduplicate + let callers = if let Some(upstream_index) = generated_snis_to_index.get(&protonode.stable_node_id) { + let ProtonodeEntry::Protonode(deduplicated_protonode) = std::mem::replace(&mut protonetwork[protonode_index], ProtonodeEntry::Deduplicated(*upstream_index)) else { + panic!("Reached protonode must not be deduplicated"); + }; + let ProtonodeEntry::Protonode(upstream_protonode) = &mut protonetwork[*upstream_index] else { + panic!("Upstream protonode must not be deduplicated"); + }; + match deduplicated_protonode.construction_args { + ConstructionArgs::Value(node_value_args) => { + let ConstructionArgs::Value(upstream_value_args) = &mut upstream_protonode.construction_args else { + panic!("Upstream protonode must match current protonode construction args"); + }; + upstream_value_args.connector_paths.extend(node_value_args.connector_paths); + } + ConstructionArgs::Nodes(node_construction_args) => { + let ConstructionArgs::Nodes(upstream_value_args) = &mut upstream_protonode.construction_args else { + panic!("Upstream protonode must match current protonode construction args"); + }; + upstream_value_args.node_paths.extend(node_construction_args.node_paths); + // The dependencies of the deduplicated node and the upstream node are the same because all inputs are the same + } + ConstructionArgs::Inline(_) => todo!(), + } + // Set the caller of the upstream node to be the minimum of all deduplicated nodes and itself + upstream_protonode.caller = deduplicated_protonode.callers.iter().chain(upstream_protonode.caller.iter()).min().cloned(); + deduplicated_protonode.callers + } else { + generated_snis_to_index.insert(protonode.stable_node_id, protonode_index); + protonode.caller = protonode.callers.iter().min().cloned(); + std::mem::take(&mut protonode.callers) + }; + + // This runs for all protonodes + for (caller_path, input_index) in callers { + let caller_index = protonode_indices[&caller_path]; + let ProtonodeEntry::Protonode(caller_protonode) = &mut protonetwork[caller_index] else { + panic!("Downstream caller cannot be deduplicated"); + }; + match &mut caller_protonode.construction_args { + ConstructionArgs::Nodes(nodes) => { + assert!(caller_index > protonode_index, "Caller index must be higher than current index"); + let input_metadata: &mut Option = &mut nodes.inputs[input_index]; + if input_metadata.is_none() { + *input_metadata = Some(UpstreamInputMetadata { + input_sni: current_stable_node_id, + context_dependencies: protonode_context_dependencies.clone(), + }) + } + } + // Value node cannot be a caller + ConstructionArgs::Value(_) => unreachable!(), + ConstructionArgs::Inline(_) => todo!(), } } } - // Do another traversal now that the caller SNI have been generated to collect metadata for the editor + // Do another traversal now that the metadata has been accumulated after deduplication + // This includes the caller of all absolute value connections which have a NodeInput::Value, as well as the caller for each protonode let mut value_connector_callers = Vec::new(); let mut protonode_callers = Vec::new(); + // Collect caller ids into a separate vec so that the pronetwork can be mutably iterated over to take the connector/node paths rather than cloning + let calling_protonode_ids = protonetwork + .iter() + .map(|entry| match entry { + ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id, + ProtonodeEntry::Deduplicated(upstream_protonode_index) => { + let ProtonodeEntry::Protonode(proto_node) = &protonetwork[*upstream_protonode_index] else { + panic!("Upstream protonode index must not be dedeuplicated"); + }; + proto_node.stable_node_id + } + }) + .collect::>(); - 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(); - - 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))); - } - - if let Some(protonode_path) = protonode_path.take() { - protonode_callers.push((protonode_path, (protonode_id, input_index))); + for protonode_entry in &mut protonetwork { + if let ProtonodeEntry::Protonode(protonode) = protonode_entry { + if let Some((caller_path, caller_input_index)) = protonode.caller.as_ref() { + let caller_index = protonode_indices[caller_path]; + match &mut protonode.construction_args { + ConstructionArgs::Value(node_value_args) => { + value_connector_callers.push((std::mem::take(&mut node_value_args.connector_paths), (calling_protonode_ids[caller_index], *caller_input_index))) + } + ConstructionArgs::Nodes(node_construction_args) => { + protonode_callers.push((std::mem::take(&mut node_construction_args.node_paths), (calling_protonode_ids[caller_index], *caller_input_index))) + } + ConstructionArgs::Inline(_) => todo!(), + } + } } } - 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)) + log::debug!("protonetwork: {:?}", protonetwork); + Ok((ProtoNetwork::from_vec(protonetwork), value_connector_callers, protonode_callers)) } fn get_input_from_absolute_connector(&mut self, traversal_input: &AbsoluteInputConnector) -> Option<&mut NodeInput> { @@ -632,39 +709,32 @@ impl NodeNetwork { } } } - // 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 + + // Performs a recursive graph traversal starting from the root export across all node inputs + // Inserts values into the protonetwork 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 + // None indicates that the caller is the traversal start, which is skipped // // 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 + protonetwork: &mut Vec, // None represents a deduplicated value node + // Every time a value input is reached, it is added to a mapping so if it reached again, it can be moved to the end of the protonetwork + value_protonode_indices: &mut HashMap, + // Every time a protonode is reached, is it added to a mapping so if it reached again, it can be moved to the end of the protonetwork + protonode_indices: &mut HashMap, + // The original location of the current traversal traversal_input: AbsoluteInputConnector, - // Protonode index, input index - traversal_start: (usize, usize), + // The protnode input which started the traversal. None if it is called from the root export + traversal_start: Option<(ProtonodePath, usize)>, ) { let network_path = &traversal_input.network_path; @@ -730,90 +800,111 @@ impl NodeNetwork { 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); + self.traverse_input(protonetwork, value_protonode_indices, 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), + // Check if the protonode has already been reached + let reached_protonode = match protonode_indices.get(&upstream_node_path) { + // The protonode has already been inserted, add the caller and node path to its metadata + Some(previous_protonode_index) => { + let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[*previous_protonode_index] else { + panic!("Previously inserted protonode must exist at mapped protonode index"); }; - 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 + protonode + } + // Construct the protonode and traverse over inputs + None => { + let number_of_inputs = upstream_document_node.inputs.len(); + let identifier = protonode_id.clone(); for input_index in 0..upstream_document_node.inputs.len() { self.traverse_input( protonetwork, - value_connector, - protonode_path, - calling_protonodes, + value_protonode_indices, protonode_indices, AbsoluteInputConnector { network_path: network_path.clone(), connector: InputConnector::node(upstream_node_id, input_index), }, - (new_protonode_index, input_index), + Some((upstream_node_path.clone(), input_index)), ); } - new_protonode_index + let context_dependencies = NODE_CONTEXT_DEPENDENCY.lock().unwrap().get(identifier.name.as_ref()).cloned().unwrap_or_default(); + let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs { + identifier, + inputs: vec![None; number_of_inputs], + context_dependencies, + node_paths: Vec::new(), + }); + let protonode = ProtoNode { + construction_args, + // All protonodes take Context by default + input: concrete!(Context), + stable_node_id: NodeId(0), + callers: Vec::new(), + caller: None, + }; + let new_protonode_index = protonetwork.len(); + protonetwork.push(ProtonodeEntry::Protonode(protonode)); + protonode_indices.insert(upstream_node_path.clone(), new_protonode_index); + let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else { + panic!("Inserted protonode must exist at new_protonode_index"); + }; + protonode } }; - calling_protonodes.entry(reached_protonode_index).or_insert_with(Vec::new).push(traversal_start); + // Only add the traversal start if it is not the root export + if let Some(traversal_start) = traversal_start { + reached_protonode.callers.push(traversal_start); + } + let ConstructionArgs::Nodes(args) = &mut reached_protonode.construction_args else { + panic!("Reached protonode must have Nodes construction args"); + }; + args.node_paths.push(upstream_node_path); } 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, + // Check if the protonode has already been reached + let reached_protonode = match value_protonode_indices.get(&traversal_input) { + // The protonode has already been inserted, add the caller and node path to its metadata + Some(previous_protonode_index) => { + let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[*previous_protonode_index] else { + panic!("Previously inserted protonode must exist at mapped protonode index"); + }; + protonode + } // Insert the protonode and traverse over inputs None => { - let protonode = ProtoNode { - construction_args: ConstructionArgs::Value(std::mem::replace(tagged_value, TaggedValue::None.into())), + let value_protonode = ProtoNode { + construction_args: ConstructionArgs::Value(NodeValueArgs { + value: std::mem::replace(tagged_value, TaggedValue::None.into()), + connector_paths: Vec::new(), + }), input: concrete!(Context), // Could be () - original_location: OriginalLocation { - protonode_path: Vec::new().into(), - send_types_to_editor: false, - }, stable_node_id: NodeId(0), + callers: Vec::new(), + caller: None, }; 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 + protonetwork.push(ProtonodeEntry::Protonode(value_protonode)); + value_protonode_indices.insert(traversal_input.clone(), new_protonode_index); + + let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else { + panic!("Previously inserted protonode must exist at mapped protonode index"); + }; + protonode } }; - calling_protonodes.entry(value_protonode_index).or_insert_with(Vec::new).push(traversal_start); + + // Only add the traversal start if it is not the root export + if let Some(traversal_start) = traversal_start { + reached_protonode.callers.push(traversal_start); + } + let ConstructionArgs::Value(args) = &mut reached_protonode.construction_args else { + panic!("Reached protonode must have Nodes construction args"); + }; + args.connector_paths.push(traversal_input); } // Continue traversal NodeInput::Network { import_index, .. } => { @@ -823,35 +914,14 @@ impl NodeNetwork { 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); + self.traverse_input(protonetwork, value_protonode_indices, protonode_indices, traversal_input, traversal_start); } - NodeInput::Scope(_cow) => unreachable!(), - NodeInput::Reflection(_document_node_metadata) => unreachable!(), - NodeInput::Inline(_inline_rust) => todo!(), + NodeInput::Scope(_) => unreachable!(), + NodeInput::Reflection(_) => unreachable!(), + NodeInput::Inline(_) => todo!(), } } - // 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(_) => {} - // } - // } - /// 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. @@ -898,11 +968,17 @@ impl NodeNetwork { } #[derive(Debug)] +pub enum ProtonodeEntry { + Protonode(ProtoNode), + // If deduplicated, then any upstream node which this node previously called needs to map to the new protonode + Deduplicated(usize), +} +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct CompilationMetadata { // Stored for every value input in the compiled network - pub protonode_callers_for_value: Vec<(AbsoluteInputConnector, CompiledProtonodeInput)>, + pub protonode_caller_for_values: Vec<(Vec, CompiledProtonodeInput)>, // Stored for every protonode in the compiled network - pub protonode_callers_for_node: Vec<(ProtonodePath, CompiledProtonodeInput)>, + pub protonode_caller_for_nodes: Vec<(Vec, CompiledProtonodeInput)>, pub types_to_add: Vec<(SNI, Vec)>, pub types_to_remove: Vec<(SNI, usize)>, } @@ -970,7 +1046,7 @@ pub struct AbsoluteOutputConnector { } /// Represents an output connector -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum OutputConnector { #[serde(rename = "node")] Node { diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 168e585cc..a41491076 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -1,18 +1,18 @@ use super::DocumentNode; use crate::proto::{Any as DAny, FutureAny}; -use crate::wasm_application_io::WasmEditorApi; +use crate::wasm_application_io::WasmApplicationIoValue; use dyn_any::DynAny; pub use dyn_any::StaticType; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; use graphene_application_io::SurfaceFrame; use graphene_brush::brush_cache::BrushCache; use graphene_brush::brush_stroke::BrushStroke; -use graphene_core::raster_types::CPU; +use graphene_core::raster_types::{CPU, GPU}; use graphene_core::transform::ReferencePoint; use graphene_core::uuid::NodeId; use graphene_core::vector::style::Fill; use graphene_core::{Color, MemoHash, Node, Type}; -use graphene_svg_renderer::RenderMetadata; +use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata}; use std::fmt::Display; use std::hash::Hash; use std::marker::PhantomData; @@ -32,10 +32,9 @@ macro_rules! tagged_value { $( $(#[$meta] ) *$identifier( $ty ), )* RenderOutput(RenderOutput), SurfaceFrame(SurfaceFrame), - #[serde(skip)] - EditorApi(Arc) } + // We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below) #[allow(clippy::derived_hash_with_manual_eq)] impl Hash for TaggedValue { @@ -46,7 +45,6 @@ macro_rules! tagged_value { $( Self::$identifier(x) => {x.hash(state)}),* Self::RenderOutput(x) => x.hash(state), Self::SurfaceFrame(x) => x.hash(state), - Self::EditorApi(x) => x.hash(state), } } } @@ -58,7 +56,6 @@ macro_rules! tagged_value { $( Self::$identifier(x) => Box::new(x), )* Self::RenderOutput(x) => Box::new(x), Self::SurfaceFrame(x) => Box::new(x), - Self::EditorApi(x) => Box::new(x), } } /// Converts to a Arc @@ -68,7 +65,6 @@ macro_rules! tagged_value { $( Self::$identifier(x) => Arc::new(x), )* Self::RenderOutput(x) => Arc::new(x), Self::SurfaceFrame(x) => Arc::new(x), - Self::EditorApi(x) => Arc::new(x), } } /// Creates a graphene_core::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value @@ -78,7 +74,6 @@ macro_rules! tagged_value { $( Self::$identifier(_) => concrete!($ty), )* Self::RenderOutput(_) => concrete!(RenderOutput), Self::SurfaceFrame(_) => concrete!(SurfaceFrame), - Self::EditorApi(_) => concrete!(&WasmEditorApi) } } /// Attempts to downcast the dynamic type to a tagged value @@ -115,7 +110,6 @@ macro_rules! tagged_value { $(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 { @@ -258,6 +252,9 @@ tagged_value! { ReferencePoint(graphene_core::transform::ReferencePoint), CentroidType(graphene_core::vector::misc::CentroidType), BooleanOperation(graphene_path_bool::BooleanOperation), + EditorMetadata(EditorMetadata), + #[serde(skip)] + ApplicationIo(Arc), } impl TaggedValue { @@ -382,18 +379,6 @@ 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 { @@ -441,6 +426,8 @@ impl + Sync + Send, U: Sync + Send> UpcastAsRefNode { } } + + #[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] pub struct RenderOutput { pub data: RenderOutputType, @@ -460,6 +447,47 @@ impl Hash for RenderOutput { } } +impl Default for RenderOutput { + fn default() -> Self { + RenderOutput { + data: RenderOutputType::Image(Vec::new()), + metadata: RenderMetadata::default(), + } + } +} + +// Passed as a scope input +#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +pub struct EditorMetadata { + // pub imaginate_hostname: String, + pub use_vello: bool, + pub hide_artboards: bool, + // If exporting, hide the artboard name and do not collect metadata + pub for_export: bool, + pub view_mode: graphene_core::vector::style::ViewMode, + pub transform_to_viewport: bool, +} + +unsafe impl dyn_any::StaticType for EditorMetadata { + type Static = EditorMetadata; +} + +impl Default for EditorMetadata { + fn default() -> Self { + Self { + // imaginate_hostname: "http://localhost:7860/".into(), + #[cfg(target_arch = "wasm32")] + use_vello: false, + #[cfg(not(target_arch = "wasm32"))] + use_vello: true, + hide_artboards: false, + for_export: false, + view_mode: graphene_core::vector::style::ViewMode::Normal, + transform_to_viewport: true, + } + } +} + /// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack) trait FakeHash { fn hash(&self, state: &mut H); @@ -509,3 +537,47 @@ mod fake_hash { } } } + +macro_rules! thumbnail_render { + ( $( $ty:ty ),* $(,)? ) => { + pub fn render_thumbnail_if_change(new_value: &Arc, old_value: Option<&Arc>) -> ThumbnailRenderResult { + $( + if let Some(new_value) = new_value.downcast_ref::<$ty>() { + match old_value { + None => return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail()), + Some(old_value) => { + if let Some(old_value) = old_value.downcast_ref::<$ty>() { + match new_value == old_value { + true => return ThumbnailRenderResult::NoChange, + false => return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail()) + } + } else { + return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail()) + } + }, + } + } + )* + return ThumbnailRenderResult::ClearThumbnail; + } + }; +} + +thumbnail_render! { + graphene_core::GraphicGroupTable, + graphene_core::vector::VectorDataTable, + graphene_core::Artboard, + graphene_core::ArtboardGroupTable, + graphene_core::raster_types::RasterDataTable, + graphene_core::raster_types::RasterDataTable, + graphene_core::GraphicElement, + Option, + Vec, +} + +pub enum ThumbnailRenderResult { + NoChange, + // Cleared if there is an error or the data could not be rendered + ClearThumbnail, + UpdateThumbnail(String), +} diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index b10c813fa..54ec938fe 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -1,4 +1,4 @@ -use crate::document::{InlineRust, value}; +use crate::document::{AbsoluteInputConnector, InlineRust, ProtonodeEntry, value}; pub use graphene_core::registry::*; use graphene_core::uuid::{NodeId, ProtonodePath, SNI}; use graphene_core::*; @@ -6,18 +6,43 @@ use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Debug; use std::hash::Hash; +use std::ops::Deref; -// #[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)] +/// 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 { + /// A list of nodes stored in a Vec to allow for sorting. + nodes: Vec, + /// The most downstream node in the protonetwork + pub output: NodeId, +} + +impl ProtoNetwork { + pub fn from_vec(nodes: Vec) -> Self { + let last_entry = nodes.last().expect("Cannot compile empty protonetwork"); + let output = match last_entry { + ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id, + ProtonodeEntry::Deduplicated(deduplicated_index) => { + let ProtonodeEntry::Protonode(protonode) = &nodes[*deduplicated_index] else { + panic!("Deduplicated protonode must point to valid protonode"); + }; + protonode.stable_node_id + } + }; + ProtoNetwork { nodes, output } + } + + pub fn nodes(&self) -> impl Iterator { + self.nodes + .iter() + .filter_map(|entry| if let ProtonodeEntry::Protonode(protonode) = entry { Some(protonode) } else { None }) + } + pub fn into_nodes(self) -> impl Iterator { + self.nodes + .into_iter() + .filter_map(|entry| if let ProtonodeEntry::Protonode(protonode) = entry { Some(protonode) } else { None }) + } +} // impl core::fmt::Display for ProtoNetwork { // fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -69,59 +94,57 @@ use std::hash::Hash; // } // } -#[derive(Debug, Clone, PartialEq, Hash, Eq)] +#[derive(Clone, Debug)] +pub struct UpstreamInputMetadata { + pub input_sni: SNI, + // Context dependencies are accumulated during compilation, then replaced with whatever needs to be nullified + // If None, then the upstream node is a value node, so replace with an empty vec + pub context_dependencies: Option>, +} + +#[derive(Debug, Clone)] 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, + // A node is dependent on whatever is marked in its implementation, as well as all inputs + // If a node is dependent on more than its input, then a context nullification node is placed on the input + // Starts as None, and is populated during stable node id generation + pub inputs: Vec>, + // The union of all input context dependencies and the nodes context dependency. Used to generate the context nullification for the editor entry point + pub context_dependencies: Vec, + // Stores the path of document nodes which correspond to it + pub node_paths: Vec, } -#[derive(Debug, Clone, PartialEq)] + +#[derive(Debug, Clone)] +pub struct NodeValueArgs { + /// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe) + /// Also stores its caller inputs, which is used to map the rendered thumbnail to the wire input + pub value: MemoHash, + // Stores all absolute input connectors which correspond to this value. + pub connector_paths: Vec, +} + +#[derive(Debug, Clone)] /// 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), + Value(NodeValueArgs), Nodes(NodeConstructionArgs), /// Used for GPU computation to work around the limitations of rust-gpu. Inline(InlineRust), } -impl Eq for ConstructionArgs {} - -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.inputs { - node.hash(state); - } - } - Self::Value(value) => value.hash(state), - Self::Inline(inline) => inline.hash(state), - } - } -} - -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.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(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, -} +// 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.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)] /// A proto node is an intermediate step between the `DocumentNode` and the boxed struct that actually runs the node (found in the [`BorrowTree`]). @@ -130,43 +153,61 @@ pub struct OriginalLocation { pub struct ProtoNode { pub construction_args: ConstructionArgs, pub input: Type, - pub original_location: OriginalLocation, pub stable_node_id: SNI, + // Each protonode stores the path and input index of the protonodes which called it + pub callers: Vec<(ProtonodePath, usize)>, + // Each protonode will finally store a single caller (the minimum of all callers), used by the editor + pub caller: Option<(ProtonodePath, usize)>, } impl Default for ProtoNode { fn default() -> Self { Self { - construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()), + construction_args: ConstructionArgs::Value(NodeValueArgs { + value: value::TaggedValue::U32(0).into(), + connector_paths: Vec::new(), + }), input: concrete!(Context), - original_location: Default::default(), stable_node_id: NodeId(0), + callers: Vec::new(), + caller: None, } } } impl ProtoNode { /// Construct a new [`ProtoNode`] with the specified construction args and a `ClonedNode` implementation. - pub fn value(value: ConstructionArgs, path: Vec, stable_node_id: SNI) -> Self { - let inputs_exposed = match &value { - ConstructionArgs::Nodes(nodes) => nodes.inputs.len() + 1, - _ => 2, - }; + pub fn value(value: ConstructionArgs, stable_node_id: SNI) -> Self { Self { construction_args: value, input: concrete!(Context), - original_location: OriginalLocation { - protonode_path: path.into(), - send_types_to_editor: false, - }, stable_node_id, + callers: Vec::new(), + caller: None, } } + + // Hashes the inputs and implementation of non value nodes, and the value for value nodes + pub fn generate_stable_node_id(&mut self) { + use std::hash::Hasher; + let mut hasher = rustc_hash::FxHasher::default(); + match &self.construction_args { + ConstructionArgs::Nodes(nodes) => { + for upstream_input in &nodes.inputs { + upstream_input.as_ref().unwrap().input_sni.hash(&mut hasher); + } + nodes.identifier.hash(&mut hasher); + } + ConstructionArgs::Value(value) => value.value.hash(&mut hasher), + ConstructionArgs::Inline(_) => todo!(), + } + + self.stable_node_id = NodeId(hasher.finish()); + } } #[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum GraphErrorType { - NodeNotFound(NodeId), InputNodeNotFound(NodeId), UnexpectedGenerics { index: usize, inputs: Vec }, NoImplementations, @@ -178,7 +219,6 @@ impl Debug for GraphErrorType { // TODO: format with the document graph context so the input index is the same as in the graph UI. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - GraphErrorType::NodeNotFound(id) => write!(f, "Input node {id} is not present in the typing context"), GraphErrorType::InputNodeNotFound(id) => write!(f, "Input node {id} is not present in the typing context"), GraphErrorType::UnexpectedGenerics { index, inputs } => write!(f, "Generic inputs should not exist but found at {index}: {inputs:?}"), GraphErrorType::NoImplementations => write!(f, "No implementations found"), @@ -222,7 +262,7 @@ impl Debug for GraphErrorType { } #[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct GraphError { - pub node_path: Vec, + pub stable_node_id: SNI, pub identifier: Cow<'static, str>, pub error: GraphErrorType, } @@ -231,11 +271,11 @@ impl GraphError { 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(), + ConstructionArgs::Value(node_value_args) => format!("{:?} Value Node", node_value_args.value.deref().ty()).into(), + ConstructionArgs::Inline(_) => "Inline".into(), }; Self { - node_path: node.original_location.protonode_path.to_vec(), + stable_node_id: node.stable_node_id, identifier, error: text.into(), } @@ -243,11 +283,7 @@ impl GraphError { } impl Debug for GraphError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NodeGraphError") - .field("path", &self.node_path.iter().map(|id| id.0).collect::>()) - .field("identifier", &self.identifier.to_string()) - .field("error", &self.error) - .finish() + f.debug_struct("NodeGraphError").field("identifier", &self.identifier.to_string()).field("error", &self.error).finish() } } pub type GraphErrors = Vec; @@ -256,17 +292,17 @@ pub type GraphErrors = Vec; #[derive(Default, Clone, dyn_any::DynAny)] pub struct TypingContext { lookup: Cow<'static, HashMap>>, - monitor_lookup: Cow<'static, HashMap>, + cache_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>, monitor_lookup: &'static HashMap) -> Self { + pub fn new(lookup: &'static HashMap>, cache_lookup: &'static HashMap) -> Self { Self { lookup: Cow::Borrowed(lookup), - monitor_lookup: Cow::Borrowed(monitor_lookup), + cache_lookup: Cow::Borrowed(cache_lookup), ..Default::default() } } @@ -274,9 +310,9 @@ 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: &Vec) -> Result<(), GraphErrors> { + pub fn update(&mut self, network: &ProtoNetwork) -> Result<(), GraphErrors> { // Update types from the most upstream nodes first - for node in network.iter().rev() { + for node in network.nodes() { self.infer(node.stable_node_id, node)?; } Ok(()) @@ -292,9 +328,9 @@ 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 cache node constructor for a given type { + pub fn cache_constructor(&self, cache_type: &Type) -> Option { + self.cache_lookup.get(cache_type).copied() } /// Returns the type of a given node id if it exists @@ -314,7 +350,7 @@ impl TypingContext { ConstructionArgs::Value(ref v) => { // 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![]); + let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.value.ty())), vec![]); self.inferred.insert(node_id, types.clone()); return Ok(types); } @@ -323,10 +359,11 @@ impl TypingContext { let inputs = construction_args .inputs .iter() + .map(|id| id.as_ref().unwrap().input_sni) .map(|id| { self.inferred - .get(id) - .ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NodeNotFound(*id))]) + .get(&id) + .ok_or_else(|| vec![GraphError::new(node, GraphErrorType::InputNodeNotFound(id))]) .map(|node| node.ty()) }) .collect::, GraphErrors>>()?; diff --git a/node-graph/graph-craft/src/wasm_application_io.rs b/node-graph/graph-craft/src/wasm_application_io.rs index 1d11744aa..405dfcbd9 100644 --- a/node-graph/graph-craft/src/wasm_application_io.rs +++ b/node-graph/graph-craft/src/wasm_application_io.rs @@ -1,8 +1,9 @@ use dyn_any::StaticType; -use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId}; +use graphene_application_io::{ApplicationError, ApplicationIo, ApplicationIoValue, ResourceFuture, SurfaceHandle, SurfaceId}; #[cfg(target_arch = "wasm32")] use js_sys::{Object, Reflect}; use std::collections::HashMap; +use std::hash::Hash; use std::sync::Arc; #[cfg(target_arch = "wasm32")] use std::sync::atomic::AtomicU64; @@ -56,6 +57,8 @@ unsafe impl Sync for WindowWrapper {} #[cfg(target_arch = "wasm32")] unsafe impl Send for WindowWrapper {} +pub type WasmApplicationIoValue = ApplicationIoValue; + #[derive(Debug, Default)] pub struct WasmApplicationIo { #[cfg(target_arch = "wasm32")] @@ -156,20 +159,12 @@ unsafe impl StaticType for WasmApplicationIo { type Static = WasmApplicationIo; } -impl<'a> From<&'a WasmEditorApi> for &'a WasmApplicationIo { - fn from(editor_api: &'a WasmEditorApi) -> Self { - editor_api.application_io.as_ref().unwrap() - } -} #[cfg(feature = "wgpu")] impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor { fn from(app_io: &'a WasmApplicationIo) -> Self { app_io.gpu_executor.as_ref().unwrap() } } - -pub type WasmEditorApi = graphene_application_io::EditorApi; - impl ApplicationIo for WasmApplicationIo { #[cfg(target_arch = "wasm32")] type Surface = HtmlCanvasElement; diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index fae70c577..f85a2c577 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -1,14 +1,15 @@ use clap::{Args, Parser, Subcommand}; use fern::colors::{Color, ColoredLevelConfig}; use futures::executor::block_on; +use graph_craft::document::value::EditorMetadata; use graph_craft::document::*; use graph_craft::graphene_compiler::{Compiler, Executor}; use graph_craft::proto::{ProtoNetwork, ProtoNode}; use graph_craft::util::load_network; -use graph_craft::wasm_application_io::EditorPreferences; +use graph_craft::wasm_application_io::{EditorPreferences, WasmApplicationIoValue}; use graphene_core::text::FontCache; -use graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; -use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; +use graphene_std::application_io::{ApplicationIo, ApplicationIoValue, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; +use graphene_std::wasm_application_io::WasmApplicationIo; use interpreted_executor::dynamic_executor::DynamicExecutor; use interpreted_executor::util::wrap_network_in_scope; use std::error::Error; @@ -92,14 +93,9 @@ async fn main() -> Result<(), Box> { use_vello: true, ..Default::default() }; - let editor_api = Arc::new(WasmEditorApi { - font_cache: FontCache::default(), - application_io: Some(application_io.into()), - node_graph_message_sender: Box::new(UpdateLogger {}), - editor_preferences: Box::new(preferences), - }); + let application_io = Arc::new(ApplicationIoValue(Some(Arc::new(application_io)))); - let proto_graph = compile_graph(document_string, editor_api)?; + let proto_graph = compile_graph(document_string, application_io)?; match app.command { Command::Compile { print_proto, .. } => { @@ -180,17 +176,16 @@ fn fix_nodes(network: &mut NodeNetwork) { } } } -fn compile_graph(document_string: String, editor_api: Arc) -> Result, Box> { +fn compile_graph(document_string: String, application_io: Arc) -> Result> { let mut network = load_network(&document_string); fix_nodes(&mut network); - let substitutions = preprocessor::generate_node_substitutions(); + let substitutions: std::collections::HashMap = preprocessor::generate_node_substitutions(); preprocessor::expand_network(&mut network, &substitutions); - let mut wrapped_network = wrap_network_in_scope(network.clone(), editor_api); + let mut wrapped_network = wrap_network_in_scope(network, Arc::new(FontCache::default()), EditorMetadata::default(), application_io); - let compiler = Compiler {}; - wrapped_network.flatten().map(|result|result.0).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/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 1a38d65b3..a93f612a6 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -1,9 +1,14 @@ use dyn_any::StaticType; +use glam::DAffine2; pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode}; use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer}; +use graphene_core::Context; +use graphene_core::ContextDependency; use graphene_core::NodeIO; +use graphene_core::OwnedContextImpl; use graphene_core::WasmNotSend; pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode}; +use graphene_core::transform::Footprint; pub use graphene_core::{Node, generic, ops}; pub trait IntoTypeErasedNode<'n> { @@ -46,3 +51,115 @@ pub fn input_node(n: SharedNodeContainer) -> DowncastBothNode<(), pub fn downcast_node(n: SharedNodeContainer) -> DowncastBothNode { DowncastBothNode::new(n) } + +pub struct EditorContextToContext { + first: SharedNodeContainer, +} + +impl<'i> Node<'i, Any<'i>> for EditorContextToContext { + type Output = DynFuture<'i, Any<'i>>; + fn eval(&'i self, input: Any<'i>) -> Self::Output { + Box::pin(async move { + let editor_context = dyn_any::downcast::(input).unwrap(); + log::debug!("evaluating with context: {:?}", editor_context.to_context()); + self.first.eval(Box::new(editor_context.to_context())).await + }) + } +} + +impl EditorContextToContext { + pub const fn new(first: SharedNodeContainer) -> Self { + EditorContextToContext { first } + } +} + +#[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, + // #[serde(skip)] + // pub editor_var_args: Option<(Vec, Vec>>)>, +} + +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) -> 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() + } +} + +pub struct NullificationNode { + first: SharedNodeContainer, + nullify: Vec, +} +impl<'i> Node<'i, Any<'i>> for NullificationNode { + type Output = DynFuture<'i, Any<'i>>; + + fn eval(&'i self, input: Any<'i>) -> Self::Output { + let new_input = match dyn_any::try_downcast::(input) { + Ok(context) => match *context { + Some(context) => { + log::debug!("Nullifying inputs: {:?}", self.nullify); + let mut new_context = OwnedContextImpl::from(context); + new_context.nullify(&self.nullify); + Box::new(new_context.into_context()) as Any<'i> + } + None => { + let none: Context = None; + Box::new(none) as Any<'i> + } + }, + Err(other_input) => other_input, + }; + + Box::pin(async move { self.first.eval(new_input).await }) + } +} + +impl NullificationNode { + pub fn new(first: SharedNodeContainer, nullify: Vec) -> Self { + Self { first, nullify } + } +} diff --git a/node-graph/gstd/src/text.rs b/node-graph/gstd/src/text.rs index ec5df2522..cd05cb31c 100644 --- a/node-graph/gstd/src/text.rs +++ b/node-graph/gstd/src/text.rs @@ -1,12 +1,11 @@ -use crate::vector::VectorDataTable; -use graph_craft::wasm_application_io::WasmEditorApi; +use crate::vector::{VectorData, VectorDataTable}; use graphene_core::Ctx; pub use graphene_core::text::*; #[node_macro::node(category(""))] fn text<'i: 'n>( _: impl Ctx, - editor: &'i WasmEditorApi, + font_cache: std::sync::Arc, text: String, font_name: Font, #[unit(" px")] @@ -41,7 +40,7 @@ fn text<'i: 'n>( tilt, }; - let font_data = editor.font_cache.get(&font_name).map(|f| load_font(f)); + let font_data = font_cache.get(&font_name).map(|f| load_font(f)); to_path(&text, font_data, typesetting, per_glyph_instances) } diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index ae03edd42..6480bf777 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -1,16 +1,16 @@ -use graph_craft::document::value::RenderOutput; pub use graph_craft::document::value::RenderOutputType; +use graph_craft::document::value::{EditorMetadata, RenderOutput}; pub use graph_craft::wasm_application_io::*; -use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig}; +use graphene_application_io::ApplicationIo; #[cfg(target_arch = "wasm32")] use graphene_core::instances::Instances; #[cfg(target_arch = "wasm32")] use graphene_core::math::bbox::Bbox; use graphene_core::raster::image::Image; -use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; +use graphene_core::raster_types::{CPU, GPU, Raster, RasterDataTable}; use graphene_core::transform::Footprint; use graphene_core::vector::VectorDataTable; -use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend}; +use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, WasmNotSend}; use graphene_svg_renderer::RenderMetadata; use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix}; @@ -26,8 +26,8 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; #[cfg(feature = "wgpu")] #[node_macro::node(category("Debug: GPU"))] -async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc { - Arc::new(editor.application_io.as_ref().unwrap().create_window()) +async fn create_surface<'a: 'n>(_: impl Ctx, application_io: WasmApplicationIoValue) -> Arc { + Arc::new(application_io.0.as_ref().unwrap().create_window()) } // TODO: Fix and reenable in order to get the 'Draw Canvas' node working again. @@ -59,20 +59,20 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> { - let Some(api) = editor.application_io.as_ref() else { - return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); - }; - let Ok(data) = api.load_resource(url) else { - return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); - }; - let Ok(data) = data.await else { - return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); - }; +// #[node_macro::node(category("Web Request"))] +// async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> { +// let Some(api) = editor.application_io.as_ref() else { +// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); +// }; +// let Ok(data) = api.load_resource(url) else { +// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); +// }; +// let Ok(data) = data.await else { +// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); +// }; - data -} +// data +// } #[node_macro::node(category("Web Request"))] fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> RasterDataTable { @@ -118,16 +118,16 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p #[cfg(feature = "vello")] #[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))] async fn render_canvas( - render_config: RenderConfig, + footprint: Footprint, + hide_artboards: bool, data: impl GraphicElementRendered, - editor: &WasmEditorApi, + application_io: Arc, surface_handle: wgpu_executor::WgpuSurface, render_params: RenderParams, ) -> RenderOutputType { use graphene_application_io::SurfaceFrame; - let footprint = render_config.viewport; - let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() else { + let Some(exec) = application_io.0.as_ref().unwrap().gpu_executor() else { unreachable!("Attempted to render with Vello when no GPU executor is available"); }; use vello::*; @@ -142,7 +142,7 @@ async fn render_canvas( scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array()))); let mut background = Color::from_rgb8_srgb(0x22, 0x22, 0x22); - if !data.contains_artboard() && !render_config.hide_artboards { + if !data.contains_artboard() && !hide_artboards { background = Color::WHITE; } exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context, background) @@ -151,7 +151,7 @@ async fn render_canvas( let frame = SurfaceFrame { surface_id: surface_handle.window_id, - resolution: render_config.viewport.resolution, + resolution: footprint.resolution, transform: glam::DAffine2::IDENTITY, }; @@ -230,73 +230,61 @@ where #[node_macro::node(category(""))] async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( - render_config: RenderConfig, - editor_api: impl Node, Output = &'a WasmEditorApi>, + context: impl Ctx + ExtractFootprint, + editor_metadata: EditorMetadata, + application_io: Arc, #[implementations( - Context -> VectorDataTable, - Context -> RasterDataTable, - Context -> GraphicGroupTable, - Context -> graphene_core::Artboard, - Context -> graphene_core::ArtboardGroupTable, - Context -> Option, - Context -> Vec, - Context -> bool, - Context -> f32, - Context -> f64, - Context -> String, + VectorDataTable, + RasterDataTable, + RasterDataTable, + GraphicGroupTable, + graphene_core::Artboard, + graphene_core::ArtboardGroupTable, + Option, + Vec, + bool, + f32, + f64, + String, )] - data: impl Node, Output = T>, + data: T, _surface_handle: impl Node, Output = Option>, ) -> RenderOutput { - let footprint = render_config.viewport; - let ctx = OwnedContextImpl::default() - .with_footprint(footprint) - .with_real_time(render_config.time.time) - .with_animation_time(render_config.time.animation_time.as_secs_f64()) - .into_context(); - ctx.footprint(); + let Some(footprint) = context.try_footprint().copied() else { + log::error!("Footprint must be Some when rendering"); + return RenderOutput::default(); + }; - let RenderConfig { hide_artboards, for_export, .. } = render_config; let render_params = RenderParams { - view_mode: render_config.view_mode, + view_mode: editor_metadata.view_mode, culling_bounds: None, thumbnail: false, - hide_artboards, - for_export, + hide_artboards: editor_metadata.hide_artboards, + for_export: editor_metadata.for_export, for_mask: false, alignment_parent_transform: None, }; - let data = data.eval(ctx.clone()).await; - let editor_api = editor_api.eval(None).await; - #[cfg(all(feature = "vello", not(test)))] let surface_handle = _surface_handle.eval(None).await; - let use_vello = editor_api.editor_preferences.use_vello(); + let use_vello = editor_metadata.use_vello; #[cfg(all(feature = "vello", not(test)))] let use_vello = use_vello && surface_handle.is_some(); let mut metadata = RenderMetadata::default(); data.collect_metadata(&mut metadata, footprint, None); - let output_format = render_config.export_format; - let data = match output_format { - ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint), - ExportFormat::Canvas => { - if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() { - #[cfg(all(feature = "vello", not(test)))] - return RenderOutput { - data: render_canvas(render_config, data, editor_api, surface_handle.unwrap(), render_params).await, - metadata, - }; - #[cfg(any(not(feature = "vello"), test))] - render_svg(data, SvgRender::new(), render_params, footprint) - } else { - render_svg(data, SvgRender::new(), render_params, footprint) - } - } - _ => todo!("Non-SVG render output for {output_format:?}"), + let data = if use_vello { + #[cfg(all(feature = "vello", not(test)))] + return RenderOutput { + data: render_canvas(footprint, editor_metadata.hide_artboards, data, application_io, surface_handle.unwrap(), render_params).await, + metadata, + }; + #[cfg(any(not(feature = "vello"), test))] + render_svg(data, SvgRender::new(), render_params, footprint) + } else { + render_svg(data, SvgRender::new(), render_params, footprint) }; RenderOutput { data, metadata } } diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 216a9b666..88892f5d1 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -211,6 +211,30 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity { #[cfg(feature = "vello")] fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams); + fn render_thumbnail(&self) -> String { + let bounds = self.bounding_box(DAffine2::IDENTITY, true); + + 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, + }; + + // Render the thumbnail data into an SVG string + let mut render = SvgRender::new(); + self.render_svg(&mut render, &render_params); + + // Give the SVG a viewbox and outer ... wrapper tag + // let [min, max] = bounds.unwrap_or_default(); + // render.format_svg(min, max); + + render.svg.to_svg_string() + } + /// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection. fn add_upstream_click_targets(&self, _click_targets: &mut Vec) {} diff --git a/node-graph/interpreted-executor/benches/benchmark_util.rs b/node-graph/interpreted-executor/benches/benchmark_util.rs index bc996e822..4093c33a5 100644 --- a/node-graph/interpreted-executor/benches/benchmark_util.rs +++ b/node-graph/interpreted-executor/benches/benchmark_util.rs @@ -7,7 +7,7 @@ use interpreted_executor::dynamic_executor::DynamicExecutor; pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) { let mut network = load_from_name(name); - let proto_network = network.flatten().unwrap(); + let proto_network = network.flatten().unwrap().0; let executor = block_on(DynamicExecutor::new(proto_network.0)).unwrap(); (executor, proto_network) } diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 2b0dae6ef..e07d453ff 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -1,17 +1,17 @@ -use crate::node_registry::{MONITOR_NODES, NODE_REGISTRY}; +use crate::node_registry::{CACHE_NODES, NODE_REGISTRY}; use dyn_any::StaticType; -use glam::DAffine2; -use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode}; -use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, downcast_node}; +use graph_craft::document::ProtonodeEntry; +use graph_craft::document::value::{TaggedValue, UpcastNode}; +use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, UpstreamInputMetadata}; 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::any::{EditorContext, EditorContextToContext, NullificationNode}; +use graphene_std::memo::IntrospectMode; use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI}; -use graphene_std::{NodeIOTypes, OwnedContextImpl}; +use graphene_std::{Context, MemoHash}; use std::collections::{HashMap, HashSet}; use std::error::Error; +use std::ptr::null; use std::sync::Arc; /// An executor of a node graph that does not require an online compilation server, and instead uses `Box`. @@ -32,25 +32,24 @@ impl Default for DynamicExecutor { Self { output: None, tree: Default::default(), - typing_context: TypingContext::new(&NODE_REGISTRY, &MONITOR_NODES), + typing_context: TypingContext::new(&NODE_REGISTRY, &CACHE_NODES), } } } impl DynamicExecutor { - pub async fn new(proto_network: Vec) -> Result { + pub async fn new(proto_network: ProtoNetwork) -> Result { let mut typing_context = TypingContext::default(); typing_context.update(&proto_network)?; - let output = proto_network.get(0).map(|protonode| protonode.stable_node_id); + let output = Some(proto_network.output); let tree = BorrowTree::new(proto_network, &typing_context).await?; - 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: Vec) -> Result<(Vec<(SNI, Vec)>, Vec<(SNI, usize)>), GraphErrors> { - self.output = proto_network.get(0).map(|protonode| protonode.stable_node_id); + pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, Vec)>, Vec<(SNI, usize)>), GraphErrors> { + self.output = Some(proto_network.output); self.typing_context.update(&proto_network)?; // 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. @@ -58,12 +57,9 @@ impl DynamicExecutor { 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()); + if let Some(number_of_inputs) = self.tree.free_node(&sni) { + remove.push((sni, number_of_inputs)); + } self.typing_context.remove_inference(&sni); } @@ -71,7 +67,6 @@ impl DynamicExecutor { .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())) @@ -82,24 +77,27 @@ impl DynamicExecutor { } /// 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 introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result>, IntrospectError> { + let node = self.get_introspect_node_container(protonode_input)?; + Ok(node.introspect(introspect_mode)) } pub fn set_introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) { - let Ok(node) = self.get_monitor_node_container(protonode_input) else { + let Ok(node) = self.get_introspect_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 { + pub fn get_introspect_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()) + let inserted_node = self.tree.nodes.get(&protonode_input.0).ok_or(IntrospectError::ProtoNodeNotFound(protonode_input))?; + let node = inserted_node + .input_introspection_entrypoints + .get(protonode_input.1) + .ok_or(IntrospectError::InputIndexOutOfBounds(protonode_input))?; + Ok(node.clone()) } pub fn input_type(&self) -> Option { @@ -118,15 +116,38 @@ impl DynamicExecutor { self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.return_value.clone())) } - pub fn execute(&self, input: I) -> LocalFuture<'_, Result>> + // 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())?; + // A node to convert the EditorContext to the Context is automatically inserted for each node at id-1 + let result = match input_type { + t if t == concrete!(Context) => 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 fn execute(&self, input: I, protonode_id: SNI) -> 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(output_node, input); + let result = self.tree.eval_tagged_value(protonode_id, input); let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await; match wrapped_result { @@ -138,95 +159,14 @@ impl DynamicExecutor { } }) } - - // 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) - // } } -#[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(SNI), + ProtoNodeNotFound(CompiledProtonodeInput), + InputIndexOutOfBounds(CompiledProtonodeInput), + InvalidInputType(CompiledProtonodeInput), NoData, RuntimeNotReady, IntrospectNotImplemented, @@ -236,14 +176,33 @@ impl std::fmt::Display for IntrospectError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IntrospectError::PathNotFound(path) => write!(f, "Path not found: {:?}", path), - IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {:?}", id), + IntrospectError::ProtoNodeNotFound(input) => write!(f, "ProtoNode not found: {:?}", input), 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"), + IntrospectError::InputIndexOutOfBounds(input) => write!(f, "Invalid input index: {:?}", input), + IntrospectError::InvalidInputType(input) => write!(f, "Invalid input type: {:?}", input), } } } +#[derive(Clone)] +struct InsertedProtonode { + // If the inserted protonode is a value node, then do not clear types when removing + is_value: bool, + // Either the value node, cache node, or protonode if output is not clone + cached_protonode: SharedNodeContainer, + // Value nodes are the entry points, since they can be directly evaluated + // Nodes with cloneable outputs have a cache, then editor entry point + // Nodes without cloneable outputs just have an editor entry point connected to their output + output_editor_entrypoint: SharedNodeContainer, + // Nodes with inputs store references to the entry points of the upstream node + // This is used to generate thumbnails + input_thumbnail_entrypoints: Vec, + // They also store references to the upstream cache/value node, used for introspection + input_introspection_entrypoints: Vec, +} + /// A store of dynamically typed nodes and their associated source map. /// /// [`BorrowTree`] maintains two main data structures: @@ -264,51 +223,54 @@ 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, as well as the number of inserted monitor nodes - nodes: HashMap, + // A hashmap of node IDs to dynamically typed proto nodes, as well as the auto inserted MonitorCache nodes, and editor entry point + nodes: HashMap, } impl BorrowTree { - pub async fn new(proto_network: Vec, typing_context: &TypingContext) -> Result { + pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result { let mut nodes = BorrowTree::default(); - for node in proto_network { + for node in proto_network.into_nodes() { nodes.push_node(node, typing_context).await? } Ok(nodes) } /// 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> { + pub async fn update(&mut self, proto_network: ProtoNetwork, 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 { + for node in proto_network.into_nodes() { 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 { + // Do not send types for auto inserted value nodes + if matches!(node.construction_args, ConstructionArgs::Nodes(_)) { nodes_with_new_type.push(sni) } - self.push_node(node, typing_context); + self.push_node(node, typing_context).await?; } } Ok((nodes_with_new_type, old_nodes)) } - fn node_deps(&self, nodes: &[SNI]) -> Vec { - nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect() + fn node_deps(&self, input_metadata: &Vec>) -> Vec<&InsertedProtonode> { + input_metadata + .iter() + .map(|input_metadata| self.nodes.get(&input_metadata.as_ref().expect("input should be mapped during SNI generation").input_sni).unwrap()) + .collect() } - /// Evaluate the output node of the [`BorrowTree`]. + /// Evaluate any node in the borrow tree pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option where I: StaticType + 'i + Send + Sync, O: StaticType + 'i, { - let node = self.nodes.get(&id).cloned()?; - let output = node.eval(Box::new(input)); + let node = self.nodes.get(&id)?; + let output = node.output_editor_entrypoint.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. @@ -317,8 +279,8 @@ impl BorrowTree { where I: StaticType + 'static + Send + Sync, { - let inserted_node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?; - let output = inserted_node.eval(Box::new(input)); + let inserted_node = self.nodes.get(&id).ok_or("Output node not found in executor")?; + let output = inserted_node.output_editor_entrypoint.eval(Box::new(input)); TaggedValue::try_from_any(output.await) } @@ -377,12 +339,9 @@ 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: &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)); - } + pub fn free_node(&mut self, id: &SNI) -> Option { + let removed_node = self.nodes.remove(&id).expect(&format!("Could not remove node: {:?}", id)); + removed_node.is_value.then_some(removed_node.input_thumbnail_entrypoints.len()) } /// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`. @@ -400,40 +359,115 @@ 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. + /// Thumbnails is a mapping of the protonode input to the rendered thumbnail through the monitor cache node 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) => { + ConstructionArgs::Value(value_args) => { // 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 = if let TaggedValue::ApplicationIo(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_args.value); let node = Box::new(upcasted) as TypeErasedBox<'_>; - self.nodes.insert(sni, NodeContainer::new(node)); + let value_node = NodeContainer::new(node); + + let inserted_protonode = InsertedProtonode { + is_value: true, + cached_protonode: value_node.clone(), + output_editor_entrypoint: value_node, + input_thumbnail_entrypoints: Vec::new(), + input_introspection_entrypoints: Vec::new(), + }; + self.nodes.insert(sni, inserted_protonode); } ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), - ConstructionArgs::Nodes(ref node_construction_args) => { + ConstructionArgs::Nodes(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 input_thumbnail_entrypoints = construction_nodes + .iter() + .map(|inserted_protonode| inserted_protonode.output_editor_entrypoint.clone()) + .collect::>(); + let input_introspection_entrypoints = construction_nodes.iter().map(|inserted_protonode| inserted_protonode.cached_protonode.clone()).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.nodes.insert(sni, node); + // Insert nullification if necessary + let protonode_inputs = construction_nodes + .iter() + .zip(node_construction_args.inputs.into_iter()) + .map(|(inserted_protonode, input_metadata)| { + let previous_input = inserted_protonode.cached_protonode.clone(); + let input_context_dependencies = input_metadata.unwrap().context_dependencies.unwrap(); + let protonode_input = if !input_context_dependencies.is_empty() { + let nullification_node = NullificationNode::new(previous_input, input_context_dependencies); + let node = Box::new(nullification_node) as TypeErasedBox<'_>; + NodeContainer::new(node) + } else { + previous_input + }; + protonode_input + }) + .collect::>(); + + let constructor = typing_context.constructor(sni).ok_or_else(|| { + vec![GraphError { + stable_node_id: sni, + identifier: node_construction_args.identifier.name.clone(), + error: GraphErrorType::NoConstructor, + }] + })?; + let node = constructor(protonode_inputs).await; + let protonode = NodeContainer::new(node); + + let types = typing_context.type_of(sni).ok_or_else(|| { + vec![GraphError { + stable_node_id: sni, + identifier: node_construction_args.identifier.name, + error: GraphErrorType::NoConstructor, + }] + })?; + + // Insert cache nodes on the output if possible + let cached_protonode = if let Some(cache_constructor) = typing_context.cache_constructor(&types.return_value.nested_type()) { + let cache = cache_constructor(protonode); + let cache_node_container = NodeContainer::new(cache); + cache_node_container + } else { + protonode + }; + + // If the call argument is Context, insert a conversion node between EditorContext to Context so that it can be evaluated + // Also insert the nullification node to whatever the protonode is not dependent on + let mut editor_entrypoint_input = cached_protonode.clone(); + if types.call_argument == concrete!(Context) { + let nullify = graphene_std::all_context_dependencies() + .into_iter() + .filter(|dependency| !node_construction_args.context_dependencies.contains(dependency)) + .collect::>(); + if !nullify.is_empty() { + let nullification_node = NullificationNode::new(cached_protonode.clone(), nullify); + let node = Box::new(nullification_node) as TypeErasedBox<'_>; + editor_entrypoint_input = NodeContainer::new(node) + } + } + + let editor_entry_point = EditorContextToContext::new(editor_entrypoint_input); + let node = Box::new(editor_entry_point) as TypeErasedBox; + let output_editor_entrypoint = NodeContainer::new(node); + + let inserted_protonode = InsertedProtonode { + is_value: false, + cached_protonode, + output_editor_entrypoint, + input_thumbnail_entrypoints, + input_introspection_entrypoints, + }; + + self.nodes.insert(sni, inserted_protonode); } }; Ok(()) @@ -449,7 +483,7 @@ mod test { #[test] fn push_node_sync() { let mut tree = BorrowTree::default(); - let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![], NodeId(0)); + let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), NodeId(0)); let context = TypingContext::default(); let future = tree.push_node(val_1_protonode, &context); futures::executor::block_on(future).unwrap(); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 45e8b524c..c3eb7ea50 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::{MonitorConstructor, NodeConstructor, TypeErasedBox}; +use graph_craft::proto::{CacheConstructor, NodeConstructor, TypeErasedBox}; use graphene_core::raster::color::Color; use graphene_core::raster::*; use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; @@ -17,8 +17,8 @@ use graphene_std::any::DowncastBothNode; 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, monitor_node}; +use graphene_std::wasm_application_io::{WasmApplicationIoValue, WasmSurfaceHandle}; +use node_registry_macros::{async_node, cache_node, convert_node, into_node}; use once_cell::sync::Lazy; use std::collections::HashMap; #[cfg(feature = "gpu")] @@ -119,22 +119,22 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => RasterDataTable]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable]), - #[cfg(feature = "gpu")] - into_node!(from: &WasmEditorApi, to: &WgpuExecutor), + // #[cfg(feature = "gpu")] + // into_node!(from: &WasmApplicationIoValue, to: &WgpuExecutor), #[cfg(feature = "gpu")] ( ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)), |args| { Box::pin(async move { - let editor_api: DowncastBothNode = DowncastBothNode::new(args[0].clone()); + let editor_api: DowncastBothNode> = DowncastBothNode::new(args[0].clone()); let node = >::new(editor_api); let any: DynAnyNode = DynAnyNode::new(node); Box::new(any) as TypeErasedBox }) }, { - let node = >::new(graphene_std::any::PanicNode::>::new()); - let params = vec![fn_type_fut!(Context, &WasmEditorApi)]; + let node = >::new(graphene_std::any::PanicNode::>>::new()); + let params = vec![fn_type_fut!(Context, Arc)]; let mut node_io = as NodeIO<'_, Context>>::to_async_node_io(&node, params); node_io.call_argument = concrete!(::Static); node_io @@ -192,51 +192,51 @@ 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), +fn cache_nodes() -> HashMap { + let nodes: Vec<(Type, CacheConstructor)> = vec![ + cache_node!(ImageTexture), + cache_node!(VectorDataTable), + cache_node!(GraphicGroupTable), + cache_node!(GraphicElement), + cache_node!(Artboard), + cache_node!(RasterDataTable), + cache_node!(RasterDataTable), + cache_node!(graphene_core::instances::Instances), + cache_node!(String), + cache_node!(IVec2), + cache_node!(DVec2), + cache_node!(bool), + cache_node!(f64), + cache_node!(u32), + cache_node!(u64), + cache_node!(()), + cache_node!(Vec), + cache_node!(BlendMode), + cache_node!(graphene_std::transform::ReferencePoint), + cache_node!(graphene_path_bool::BooleanOperation), + cache_node!(Option), + cache_node!(graphene_core::vector::style::Fill), + cache_node!(graphene_core::vector::style::StrokeCap), + cache_node!(graphene_core::vector::style::StrokeJoin), + cache_node!(graphene_core::vector::style::PaintOrder), + cache_node!(graphene_core::vector::style::StrokeAlign), + cache_node!(graphene_core::vector::style::Stroke), + cache_node!(graphene_core::vector::style::Gradient), + cache_node!(graphene_core::vector::style::GradientStops), + cache_node!(Vec), + cache_node!(Color), + cache_node!(Box), + cache_node!(graphene_std::vector::misc::CentroidType), + cache_node!(graphene_std::vector::misc::PointSpacingType), ]; - let mut monitor_nodes = HashMap::new(); - for (monitor_type, constructor) in nodes { - monitor_nodes.insert(monitor_type, constructor); + let mut cache_nodes = HashMap::new(); + for (cache_type, constructor) in nodes { + cache_nodes.insert(cache_type, constructor); } - monitor_nodes + cache_nodes } -pub static MONITOR_NODES: Lazy> = Lazy::new(|| monitor_nodes()); +pub static CACHE_NODES: Lazy> = Lazy::new(|| cache_nodes()); mod node_registry_macros { macro_rules! async_node { @@ -331,10 +331,10 @@ mod node_registry_macros { }; } - macro_rules! monitor_node { + macro_rules! cache_node { ($type:ty) => { (concrete!($type), |arg| { - let node = >::new(graphene_std::registry::downcast_node::(arg)); + let node = >::new(graphene_std::registry::downcast_node::(arg)); let any: DynAnyNode<_, _, _> = graphene_std::any::DynAnyNode::new(node); Box::new(any) as TypeErasedBox }) @@ -342,7 +342,7 @@ mod node_registry_macros { } pub(crate) use async_node; + pub(crate) use cache_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 dd496d148..5108f1c1b 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -1,14 +1,18 @@ use graph_craft::ProtoNodeIdentifier; use graph_craft::concrete; +use graph_craft::document::value::EditorMetadata; +use graph_craft::document::value::RenderOutput; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}; use graph_craft::generic; -use graph_craft::wasm_application_io::WasmEditorApi; +use graph_craft::wasm_application_io::WasmApplicationIo; use graphene_std::Context; +use graphene_std::application_io::ApplicationIoValue; +use graphene_std::text::FontCache; use graphene_std::uuid::NodeId; use std::sync::Arc; -pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc) -> NodeNetwork { +pub fn wrap_network_in_scope(network: NodeNetwork, font_cache: Arc, editor_metadata: EditorMetadata, application_io: Arc) -> NodeNetwork { let inner_network = DocumentNode { implementation: DocumentNodeImplementation::Network(network), inputs: vec![], @@ -16,12 +20,12 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc syn::Result { @@ -346,6 +346,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result syn::Result syn::Result, + pub(crate) context_dependency: Vec, } impl Parse for Implementation { @@ -350,6 +351,7 @@ fn parse_inputs(inputs: &Punctuated) -> syn::Result<(Input, Vec TokenStream2 { impl ParsedNodeFn { fn replace_impl_trait_in_input(&mut self) { if let Type::ImplTrait(impl_trait) = self.input.ty.clone() { + let mut dependency_tokens = Vec::new(); + for bound in &impl_trait.bounds { + if let syn::TypeParamBound::Trait(trait_bound) = bound { + if let Some(ident) = trait_bound.path.get_ident() { + match ident.to_string().as_str() { + "ExtractFootprint" => dependency_tokens.push(quote::quote! {ExtractFootprint}), + "ExtractDownstreamTransform" => dependency_tokens.push(quote::quote! {ExtractDownstreamTransform}), + "ExtractRealTime" => dependency_tokens.push(quote::quote! {ExtractRealTime}), + "ExtractAnimationTime" => dependency_tokens.push(quote::quote! {ExtractAnimationTime}), + "ExtractIndex" => dependency_tokens.push(quote::quote! {ExtractIndex}), + "ExtractVarArgs" => dependency_tokens.push(quote::quote! {ExtractVarArgs}), + _ => {} + } + } + } + } + self.input.context_dependency = dependency_tokens; + let ident = Ident::new("_Input", impl_trait.span()); let mut bounds = impl_trait.bounds; bounds.push(parse_quote!('n)); @@ -768,6 +788,7 @@ mod tests { pat_ident: pat_ident("a"), ty: parse_quote!(f64), implementations: Punctuated::new(), + context_dependency: Vec::new(), }, output_type: parse_quote!(f64), is_async: false, @@ -829,6 +850,7 @@ mod tests { pat_ident: pat_ident("footprint"), ty: parse_quote!(Footprint), implementations: Punctuated::new(), + context_dependency: Vec::new(), }, output_type: parse_quote!(T), is_async: false, @@ -901,6 +923,7 @@ mod tests { pat_ident: pat_ident("_"), ty: parse_quote!(impl Ctx), implementations: Punctuated::new(), + context_dependency: Vec::new(), }, output_type: parse_quote!(VectorData), is_async: false, @@ -958,6 +981,7 @@ mod tests { pat_ident: pat_ident("image"), ty: parse_quote!(RasterDataTable

), implementations: Punctuated::new(), + context_dependency: Vec::new(), }, output_type: parse_quote!(RasterDataTable

), is_async: false, @@ -1027,6 +1051,7 @@ mod tests { pat_ident: pat_ident("a"), ty: parse_quote!(f64), implementations: Punctuated::new(), + context_dependency: Vec::new(), }, output_type: parse_quote!(f64), is_async: false, @@ -1084,6 +1109,7 @@ mod tests { pat_ident: pat_ident("api"), ty: parse_quote!(&WasmEditorApi), implementations: Punctuated::new(), + context_dependency: Vec::new(), }, output_type: parse_quote!(RasterDataTable), is_async: true, @@ -1141,6 +1167,7 @@ mod tests { pat_ident: pat_ident("input"), ty: parse_quote!(i32), implementations: Punctuated::new(), + context_dependency: Vec::new(), }, output_type: parse_quote!(i32), is_async: false, diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index d65c24814..ef790468f 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -4,7 +4,7 @@ use anyhow::Result; pub use context::Context; use dyn_any::StaticType; use glam::UVec2; -use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle}; +use graphene_application_io::{ApplicationIo, ApplicationIoValue, SurfaceHandle}; use graphene_core::{Color, Ctx}; pub use graphene_svg_renderer::RenderContext; use std::sync::Arc; @@ -23,9 +23,9 @@ impl std::fmt::Debug for WgpuExecutor { } } -impl<'a, T: ApplicationIo> From<&'a EditorApi> for &'a WgpuExecutor { - fn from(editor_api: &'a EditorApi) -> Self { - editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap() +impl<'a, Io: ApplicationIo> From<&'a Arc>> for &'a WgpuExecutor { + fn from(application_io: &'a Arc>) -> Self { + application_io.0.as_ref().unwrap().gpu_executor().unwrap() } } @@ -153,8 +153,8 @@ impl WgpuExecutor { pub type WindowHandle = Arc>; #[node_macro::node(skip_impl)] -fn create_gpu_surface<'a: 'n, Io: ApplicationIo + 'a + Send + Sync>(_: impl Ctx + 'a, editor_api: &'a EditorApi) -> Option { - let canvas = editor_api.application_io.as_ref()?.window()?; - let executor = editor_api.application_io.as_ref()?.gpu_executor()?; +fn create_gpu_surface<'a: 'n, Io: ApplicationIo + 'a + Send + Sync>(_: impl Ctx + 'a, application_io: Arc>) -> Option { + let canvas = application_io.0.as_ref()?.window()?; + let executor = application_io.0.as_ref()?.gpu_executor()?; Some(Arc::new(executor.create_surface(canvas).ok()?)) }