diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 04536890ea..9ff407843c 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -44,8 +44,11 @@ impl DispatcherMessageHandlers { /// 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::EvaluateActiveDocumentWithThumbnails), + // MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::IntrospectActiveDocument), + MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( + PropertiesPanelMessageDiscriminant::Refresh, + ))), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( PropertiesPanelMessageDiscriminant::Refresh, ))), @@ -123,13 +126,13 @@ impl Dispatcher { } } - // 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; - } - } + // // 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); @@ -144,9 +147,10 @@ impl Dispatcher { Message::EndEvaluationQueue => { self.queueing_evaluation_messages = false; } - Message::ProcessEvaluationQueue(render_output_metadata) => { + Message::ProcessEvaluationQueue(render_output_metadata, introspected_nodes) => { let update_message = PortfolioMessage::ProcessEvaluationResponse { evaluation_metadata: render_output_metadata, + introspected_nodes, } .into(); // Update the state with the render output and introspected inputs @@ -154,23 +158,9 @@ impl Dispatcher { // 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(introspection_response) => { - let update_message = PortfolioMessage::ProcessIntrospectionResponse { introspection_response }.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)); + // Clear all introspected data after the queued messages are execucted 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 => {} diff --git a/editor/src/messages/animation/animation_message_handler.rs b/editor/src/messages/animation/animation_message_handler.rs index afb9717a10..fe4eed5dd9 100644 --- a/editor/src/messages/animation/animation_message_handler.rs +++ b/editor/src/messages/animation/animation_message_handler.rs @@ -84,7 +84,7 @@ impl MessageHandler for AnimationMessageHandler { } AnimationMessage::SetFrameIndex { frame } => { self.frame_index = frame; - responses.add(PortfolioMessage::EvaluateActiveDocument); + responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails); // Update the restart and pause/play buttons responses.add(PortfolioMessage::UpdateDocumentWidgets); } @@ -100,7 +100,7 @@ impl MessageHandler for AnimationMessageHandler { } AnimationMessage::UpdateTime => { if self.is_playing() { - responses.add(PortfolioMessage::EvaluateActiveDocument); + responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails); if self.live_preview_recently_zero { // Update the restart and pause/play buttons @@ -116,7 +116,7 @@ impl MessageHandler for AnimationMessageHandler { _ => AnimationState::Stopped, }; self.live_preview_recently_zero = true; - responses.add(PortfolioMessage::EvaluateActiveDocument); + responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails); // Update the restart and pause/play buttons responses.add(PortfolioMessage::UpdateDocumentWidgets); } 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 49476d9e81..2173f2ad20 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 @@ -31,8 +31,8 @@ impl MessageHandler for NewDocumentDialogMessageHa } responses.add(Message::StartEvaluationQueue); responses.add(DocumentMessage::ZoomCanvasToFitAll); - responses.add(Message::EndEvaluationQueue); responses.add(DocumentMessage::DeselectAllLayers); + responses.add(Message::EndEvaluationQueue); } } diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index addb7491ce..c8349fedd4 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -1,15 +1,15 @@ use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::utility_types::{ - BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform, + BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeSNIUpdate, FrontendNodeType, Transform, }; use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer}; -use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate}; +use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate, WireSNIUpdate}; use crate::messages::prelude::*; use crate::messages::tool::utility_types::HintData; -use graphene_std::uuid::NodeId; use graphene_std::raster::color::Color; use graphene_std::text::Font; +use graphene_std::uuid::{NodeId, SNI}; #[impl_message(Message, Frontend)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] @@ -128,6 +128,10 @@ pub enum FrontendMessage { #[serde(rename = "box")] box_selection: Option, }, + UpdateContextDuringEvaluation { + #[serde(rename = "contextDuringEvaluation")] + context_during_evaluation: Vec<(SNI, usize, String)>, + }, UpdateContextMenuInformation { #[serde(rename = "contextMenuInformation")] context_menu_information: Option, @@ -224,6 +228,10 @@ pub enum FrontendMessage { #[serde(rename = "setColorChoice")] set_color_choice: Option, }, + UpdateGraphBreadcrumbPath { + #[serde(rename = "breadcrumbPath")] + breadcrumb_path: Vec, + }, UpdateGraphFadeArtwork { percentage: f64, }, @@ -263,7 +271,7 @@ pub enum FrontendMessage { UpdateNodeGraphWires { wires: Vec, }, - ClearAllNodeGraphWires, + ClearAllNodeGraphWirePaths, UpdateNodeGraphControlBarLayout { #[serde(rename = "layoutTarget")] layout_target: LayoutTarget, @@ -287,7 +295,10 @@ pub enum FrontendMessage { UpdateThumbnails { add: Vec<(NodeId, String)>, clear: Vec, - // remove: Vec, + #[serde(rename = "wireSNIUpdates")] + wire_sni_updates: Vec, + #[serde(rename = "layerSNIUpdates")] + layer_sni_updates: Vec, }, UpdateToolOptionsLayout { #[serde(rename = "layoutTarget")] diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index 16a4217447..761bec7d4e 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -13,12 +13,7 @@ pub enum Message { 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), + ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata, IntrospectionResponse), #[child] Animation(AnimationMessage), #[child] diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 069a2b9b29..446fd65ee5 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -442,7 +442,7 @@ impl MessageHandler> for DocumentMessag DocumentMessage::EnterNestedNetwork { node_id } => { self.breadcrumb_network_path.push(node_id); self.selection_network_path.clone_from(&self.breadcrumb_network_path); - responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::UnloadWirePaths); responses.add(NodeGraphMessage::SendGraph); responses.add(DocumentMessage::ZoomCanvasToFitAll); responses.add(NodeGraphMessage::SetGridAlignedEdges); @@ -472,7 +472,7 @@ impl MessageHandler> for DocumentMessag self.breadcrumb_network_path.pop(); self.selection_network_path.clone_from(&self.breadcrumb_network_path); } - responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::UnloadWirePaths); responses.add(NodeGraphMessage::SendGraph); responses.add(DocumentMessage::PTZUpdate); responses.add(NodeGraphMessage::SetGridAlignedEdges); @@ -539,7 +539,7 @@ impl MessageHandler> for DocumentMessag responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderScrollbars); if opened { - responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::UnloadWirePaths); } if open { responses.add(ToolMessage::DeactivateTools); @@ -1424,7 +1424,7 @@ impl MessageHandler> for DocumentMessag center: Key::Alt, duplicate: Key::Alt, })); - responses.add(PortfolioMessage::EvaluateActiveDocument); + responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails); } else { let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else { return; @@ -1440,7 +1440,8 @@ impl MessageHandler> for DocumentMessag responses.add(NodeGraphMessage::UpdateEdges); responses.add(NodeGraphMessage::UpdateBoxSelection); responses.add(NodeGraphMessage::UpdateImportsExports); - + responses.add(NodeGraphMessage::UpdateVisibleNodes); + responses.add(NodeGraphMessage::SendWirePaths); responses.add(FrontendMessage::UpdateNodeGraphTransform { transform: Transform { scale: transform.matrix2.x_axis.x, @@ -1909,10 +1910,7 @@ impl DocumentMessageHandler { responses.add(PortfolioMessage::CompileActiveDocument); responses.add(Message::StartEvaluationQueue); responses.add(PortfolioMessage::UpdateOpenDocumentsList); - responses.add(NodeGraphMessage::SelectedNodesUpdated); - responses.add(NodeGraphMessage::SetGridAlignedEdges); - responses.add(NodeGraphMessage::UnloadWires); - responses.add(NodeGraphMessage::SendWires); + responses.add(NodeGraphMessage::SendGraph); responses.add(Message::EndEvaluationQueue); Some(previous_network) } @@ -1944,8 +1942,8 @@ impl DocumentMessageHandler { // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(NodeGraphMessage::SelectedNodesUpdated); - responses.add(NodeGraphMessage::UnloadWires); - responses.add(NodeGraphMessage::SendWires); + responses.add(NodeGraphMessage::UnloadWirePaths); + responses.add(NodeGraphMessage::SendWirePaths); responses.add(Message::EndEvaluationQueue); Some(previous_network) } @@ -2576,7 +2574,7 @@ impl DocumentMessageHandler { layout: Layout::WidgetLayout(document_bar_layout), layout_target: LayoutTarget::DocumentBar, }); - responses.add(PortfolioMessage::EvaluateActiveDocument); + responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails); } pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque) { 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 e57fcf73c8..a2c08f5c12 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 @@ -138,87 +138,6 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("A default node network you can use to create your own custom nodes."), properties: None, }, - DocumentNodeDefinition { - identifier: "Cache", - category: "General", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::network(generic!(T), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::None, true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Memoize".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Freeze Real Time".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Boundless Footprint".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - input_metadata: vec![("Data", "TODO").into()], - output_names: vec!["Data".to_string()], - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, DocumentNodeDefinition { identifier: "Merge", category: "General", @@ -529,21 +448,14 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), - skip_deduplication: true, - ..Default::default() - }, - DocumentNode { - manual_composition: Some(concrete!(Context)), - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - ..Default::default() - }, - ] + exports: vec![NodeInput::node(NodeId(0), 0)], + nodes: [DocumentNode { + inputs: vec![NodeInput::scope("editor-api")], + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), + skip_deduplication: true, + cache_output: true, + ..Default::default() + }] .into_iter() .enumerate() .map(|(id, node)| (NodeId(id as u64), node)) @@ -556,24 +468,14 @@ fn static_nodes() -> Vec { output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create Canvas".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, + node_metadata: [DocumentNodeMetadata { + persistent_metadata: DocumentNodePersistentMetadata { + display_name: "Create Canvas".to_string(), + node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), ..Default::default() }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] + ..Default::default() + }] .into_iter() .enumerate() .map(|(id, node)| (NodeId(id as u64), node)) @@ -595,19 +497,14 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], + exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ DocumentNode { inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), manual_composition: Some(concrete!(Context)), skip_deduplication: true, - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - manual_composition: Some(concrete!(Context)), + cache_output: true, ..Default::default() }, DocumentNode { @@ -650,14 +547,6 @@ fn static_nodes() -> Vec { }, ..Default::default() }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)), - ..Default::default() - }, - ..Default::default() - }, DocumentNodeMetadata { persistent_metadata: DocumentNodePersistentMetadata { display_name: "Rasterize".to_string(), @@ -1408,33 +1297,14 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(3), 0)], - nodes: vec![ - DocumentNode { - inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], - implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - ] + exports: vec![NodeInput::node(NodeId(0), 0)], + nodes: vec![DocumentNode { + inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], + implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER), + manual_composition: Some(generic!(T)), + cache_output: true, + ..Default::default() + }] .into_iter() .enumerate() .map(|(id, node)| (NodeId(id as u64), node)) @@ -1450,40 +1320,14 @@ fn static_nodes() -> Vec { persistent_node_metadata: DocumentNodePersistentMetadata { network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Boolean Operation".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, + node_metadata: [DocumentNodeMetadata { + persistent_metadata: DocumentNodePersistentMetadata { + display_name: "Boolean Operation".to_string(), + node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), ..Default::default() }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Memoize".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Freeze Real Time".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Boundless Footprint".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] + ..Default::default() + }] .into_iter() .enumerate() .map(|(id, node)| (NodeId(id as u64), node)) @@ -1506,7 +1350,7 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(4), 0)], + exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)], @@ -1526,24 +1370,7 @@ fn static_nodes() -> Vec { NodeInput::node(NodeId(0), 0), ], implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(3), 0)], - implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), + cache_output: true, manual_composition: Some(generic!(T)), ..Default::default() }, @@ -1585,30 +1412,6 @@ fn static_nodes() -> Vec { }, ..Default::default() }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Memoize".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Freeze Real Time".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Boundless Footprint".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 0)), - ..Default::default() - }, - ..Default::default() - }, ] .into_iter() .enumerate() @@ -1671,96 +1474,17 @@ fn static_nodes() -> Vec { category: "Vector: Modifier", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(3), 0)], - nodes: [ - DocumentNode { - inputs: vec![ - NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0), - NodeInput::network(concrete!(f64), 1), - NodeInput::network(concrete!(u32), 2), - ], - manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), - manual_composition: Some(generic!(T)), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), inputs: vec![ - NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true), - NodeInput::value(TaggedValue::F64(10.), false), - NodeInput::value(TaggedValue::U32(0), false), + NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0), + NodeInput::network(concrete!(f64), 1), + NodeInput::network(concrete!(u32), 2), ], + cache_output: true, + manual_composition: Some(generic!(T)), + implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER), ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Poisson-Disk Points".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Memoize".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Freeze Real Time".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Boundless Footprint".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), input_metadata: vec![ ("Vector Data", "TODO").into(), InputMetadata::with_name_description_override( 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 da2344d403..8d7b7a368d 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 @@ -121,8 +121,8 @@ pub enum NodeGraphMessage { }, SendClickTargets, EndSendClickTargets, - UnloadWires, - SendWires, + UnloadWirePaths, + SendWirePaths, UpdateVisibleNodes, SendGraph, SetGridAlignedEdges, 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 8bee2952b1..b1c41b7f9b 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 @@ -6,12 +6,12 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::document_message_handler::navigation_controls; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext; -use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType}; +use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, FrontendNodeSNIUpdate}; 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, 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::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, WireSNIUpdate, build_vector_wire}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; @@ -84,7 +84,7 @@ pub struct NodeGraphMessageHandler { /// The end index of the moved port end_index: Option, /// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend - frontend_nodes: Vec, + pub frontend_nodes: Vec, /// Used to keep track of what wires are sent to the front end so the old ones can be removed frontend_wires: HashSet<(NodeId, usize)>, } @@ -918,7 +918,7 @@ impl<'a> MessageHandler> for NodeG wire_in_progress_to_connector, from_connector_is_layer, to_connector_is_layer, - GraphWireStyle::Direct, + &GraphWireStyle::Direct, ); let mut path_string = String::new(); let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); @@ -928,7 +928,6 @@ impl<'a> MessageHandler> for NodeG thick: false, dashed: false, center: None, - input_sni: None, }; responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) }); } @@ -1179,7 +1178,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::>(); @@ -1310,15 +1309,15 @@ impl<'a> MessageHandler> for NodeG click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)), }), NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }), - NodeGraphMessage::UnloadWires => { + NodeGraphMessage::UnloadWirePaths => { for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) { network_interface.unload_wire(&input, breadcrumb_network_path); } - responses.add(FrontendMessage::ClearAllNodeGraphWires); + responses.add(FrontendMessage::ClearAllNodeGraphWirePaths); } - NodeGraphMessage::SendWires => { - let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path); + NodeGraphMessage::SendWirePaths => { + let wires = self.collect_wires_paths(network_interface, &preferences.graph_wire_style, breadcrumb_network_path); responses.add(FrontendMessage::UpdateNodeGraphWires { wires }); } NodeGraphMessage::UpdateVisibleNodes => { @@ -1347,8 +1346,20 @@ impl<'a> MessageHandler> for NodeG let nodes = self.collect_nodes(network_interface, breadcrumb_network_path); self.frontend_nodes = nodes.iter().map(|node| node.id).collect(); responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes }); - responses.add(NodeGraphMessage::UpdateVisibleNodes); + let (layer_sni_updates, wire_sni_updates) = NodeGraphMessageHandler::graph_sni_updates(network_interface, breadcrumb_network_path); + responses.add(FrontendMessage::UpdateThumbnails { + add: Vec::new(), + clear: Vec::new(), + wire_sni_updates, + layer_sni_updates, + }); + responses.add(NodeGraphMessage::UpdateVisibleNodes); + responses.add(NodeGraphMessage::UnloadWirePaths); + responses.add(NodeGraphMessage::SendWirePaths); + responses.add(FrontendMessage::UpdateGraphBreadcrumbPath { + breadcrumb_path: breadcrumb_network_path.to_vec(), + }); let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path); responses.add(NodeGraphMessage::UpdateImportsExports); @@ -1366,6 +1377,7 @@ impl<'a> MessageHandler> for NodeG network_interface.set_grid_aligned_edges(DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.), breadcrumb_network_path); // Send the new edges to the frontend responses.add(NodeGraphMessage::UpdateImportsExports); + responses.add(NodeGraphMessage::SendWirePaths); } } NodeGraphMessage::SetInputValue { node_id, input_index, value } => { @@ -1378,7 +1390,7 @@ impl<'a> MessageHandler> for NodeG 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(NodeGraphMessage::SendWirePaths); responses.add(Message::EndEvaluationQueue); } } @@ -1437,7 +1449,7 @@ impl<'a> MessageHandler> for NodeG } } - responses.add(NodeGraphMessage::SendWires); + responses.add(NodeGraphMessage::SendWirePaths); } NodeGraphMessage::ToggleSelectedAsLayersOrNodes => { let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { @@ -1458,7 +1470,7 @@ impl<'a> MessageHandler> for NodeG NodeGraphMessage::ShiftNodePosition { node_id, x, y } => { network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path); - responses.add(NodeGraphMessage::SendWires); + responses.add(NodeGraphMessage::SendWirePaths); } NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => { if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) { @@ -1472,7 +1484,7 @@ impl<'a> MessageHandler> for NodeG }); responses.add(PortfolioMessage::CompileActiveDocument); responses.add(NodeGraphMessage::SendGraph); - responses.add(NodeGraphMessage::SendWires); + responses.add(NodeGraphMessage::SendWirePaths); } NodeGraphMessage::SetDisplayName { node_id, @@ -2130,7 +2142,30 @@ impl NodeGraphMessageHandler { } } - fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec { + pub fn graph_sni_updates(network_interface: &NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> (Vec, Vec) { + let mut layer_updates = Vec::new(); + let wires = network_interface + .node_graph_input_connectors(breadcrumb_network_path) + .iter() + .map(|input_connector| { + if let Some(node_id) = input_connector.node_id() { + if network_interface.is_layer(&node_id, breadcrumb_network_path) { + layer_updates.push(FrontendNodeSNIUpdate { + id: node_id, + sni: network_interface.protonode_from_output(&OutputConnector::node(node_id, 0), breadcrumb_network_path), + }) + } + } + WireSNIUpdate { + id: input_connector.node_id().unwrap_or(NodeId(u64::MAX)), + input_index: input_connector.input_index(), + sni: network_interface.protonode_from_input(input_connector, breadcrumb_network_path), + } + }) + .collect::>(); + (layer_updates, wires) + } + fn collect_wires_paths(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: &GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec { let mut added_wires = network_interface .node_graph_input_connectors(breadcrumb_network_path) .iter() @@ -2278,18 +2313,24 @@ impl NodeGraphMessageHandler { let locked = network_interface.is_locked(&node_id, breadcrumb_network_path); - let errors = None; // TODO: Recursive traversal from export over all protonodes and match metadata with error - self.node_graph_errors + let errors: Option = self + .node_graph_errors .iter() .find(|error| match &error.original_location { graph_craft::proto::OriginalLocation::Value(_) => false, - graph_craft::proto::OriginalLocation::Node(node_ids) => node_ids == &node_id_path, + graph_craft::proto::OriginalLocation::Node(prefixed_node_path) => { + let (prefix, node_path) = prefixed_node_path.split_first().unwrap(); + node_path == &node_id_path + } }) .map(|error| format!("{:?}", error.error.clone())) .or_else(|| { if self.node_graph_errors.iter().any(|error| match &error.original_location { graph_craft::proto::OriginalLocation::Value(_) => false, - graph_craft::proto::OriginalLocation::Node(node_ids) => node_ids.starts_with(&node_id_path), + graph_craft::proto::OriginalLocation::Node(prefixed_node_path) => { + let (prefix, node_path) = prefixed_node_path.split_first().unwrap(); + node_path.starts_with(&node_id_path) + } }) { Some("Node graph type error within this node".to_string()) } else { @@ -2297,11 +2338,16 @@ impl NodeGraphMessageHandler { } }); + let is_layer = network_interface + .node_metadata(&node_id, breadcrumb_network_path) + .is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()); + nodes.push(FrontendNode { id: node_id, - is_layer: network_interface - .node_metadata(&node_id, breadcrumb_network_path) - .is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()), + is_layer, + layer_thumbnail_sni: is_layer + .then(|| network_interface.protonode_from_input(&InputConnector::Node { node_id, input_index: 1 }, breadcrumb_network_path)) + .flatten(), can_be_layer: can_be_layer_lookup.contains(&node_id), reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(), display_name: network_interface.display_name(&node_id, breadcrumb_network_path), 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 af5ecf681f..7ba4424497 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -1,8 +1,8 @@ -use crate::messages::portfolio::document::utility_types::network_interface::{TypeSource}; -use graph_craft::document::{InputConnector, OutputConnector}; +use crate::messages::portfolio::document::utility_types::network_interface::TypeSource; use graph_craft::document::value::TaggedValue; -use graphene_std::uuid::NodeId; +use graph_craft::document::{InputConnector, OutputConnector}; use graphene_std::Type; +use graphene_std::uuid::NodeId; use std::borrow::Cow; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] @@ -76,6 +76,8 @@ pub struct FrontendNode { pub id: NodeId, #[serde(rename = "isLayer")] pub is_layer: bool, + #[serde(rename = "layerThumbnailSNI")] + pub layer_thumbnail_sni: Option, #[serde(rename = "canBeLayer")] pub can_be_layer: bool, pub reference: Option, @@ -98,6 +100,12 @@ pub struct FrontendNode { pub ui_only: bool, } +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct FrontendNodeSNIUpdate { + pub id: NodeId, + pub sni: Option, +} + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct FrontendNodeType { pub name: Cow<'static, str>, 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 71ac76c1da..63ab5efe34 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -2521,56 +2521,48 @@ impl NodeNetworkInterface { .find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None }) } - pub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { - if !self.wire_is_loaded(input, network_path) { - self.load_wire(input, graph_wire_style, network_path); - } else { + pub fn viewport_loaded_thumbnail_position(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option { + let wire = self.wire_path_from_input(input, graph_wire_style, false, network_path)?; + let network_metadata = self.network_metadata(network_path)?; + if wire.thick { return None; - } + }; + wire.center.map(|center| { + let node_graph_position = DVec2::new(center.0 as f64, center.1 as f64); + network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(node_graph_position) + }) + } - let wire = match input { + pub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option { + match self.cached_wire(input, network_path) { + Some(loaded) => None, + None => { + self.load_wire(input, graph_wire_style, network_path); + self.cached_wire(input, network_path).cloned() + } + } + } + + pub fn cached_wire(&self, input: &InputConnector, network_path: &[NodeId]) -> Option<&WirePathUpdate> { + match input { InputConnector::Node { node_id, input_index } => { let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?; let TransientMetadata::Loaded(wire) = &input_metadata.wire else { - log::error!("Could not load wire for input: {:?}", input); return None; }; - wire.clone() + Some(wire) } InputConnector::Export(export_index) => { let network_metadata = self.network_metadata(network_path)?; let Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else { - log::error!("Could not load wire for input: {:?}", input); return None; }; - wire.clone() - } - }; - Some(wire) - } - - pub fn wire_is_loaded(&mut self, input: &InputConnector, network_path: &[NodeId]) -> bool { - match input { - InputConnector::Node { node_id, input_index } => { - let Some(input_metadata) = self.transient_input_metadata(node_id, *input_index, network_path) else { - log::error!("Input metadata should always exist for input"); - return false; - }; - input_metadata.wire.is_loaded() - } - InputConnector::Export(export_index) => { - let Some(network_metadata) = self.network_metadata(network_path) else { - return false; - }; - match network_metadata.transient_metadata.wires.get(*export_index) { - Some(wire) => wire.is_loaded(), - None => false, - } + Some(wire) } } } - fn load_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) { + fn load_wire(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) { let dashed = match self.previewing(network_path) { Previewing::Yes { .. } => match input { InputConnector::Node { .. } => false, @@ -2698,7 +2690,7 @@ impl NodeNetworkInterface { } /// When previewing, there may be a second path to the root node. - pub fn wire_to_root(&mut self, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { + pub fn wire_to_root(&mut self, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option { let input = InputConnector::Export(0); let current_export = self.upstream_output_connector(&input, network_path)?; @@ -2733,7 +2725,6 @@ impl NodeNetworkInterface { thick, dashed: false, center: None, - input_sni: None, }); Some(WirePathUpdate { @@ -2744,7 +2735,7 @@ 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, DVec2)> { + 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; @@ -2760,23 +2751,21 @@ 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; - let (wire, center) = build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style); + 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 { + pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option { 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.protonode_from_input(input, network_path); Some(WirePath { path_string, data_type, thick, dashed, - center: Some((center.x, center.y)), - input_sni, + center: Some((center.x as i32, center.y as i32)), }) } diff --git a/editor/src/messages/portfolio/document/utility_types/wires.rs b/editor/src/messages/portfolio/document/utility_types/wires.rs index e37dc324a8..fcd9e894f5 100644 --- a/editor/src/messages/portfolio/document/utility_types/wires.rs +++ b/editor/src/messages/portfolio/document/utility_types/wires.rs @@ -12,9 +12,7 @@ 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, + pub center: Option<(i32, i32)>, } #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] @@ -27,6 +25,14 @@ pub struct WirePathUpdate { pub wire_path_update: Option, } +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct WireSNIUpdate { + pub id: NodeId, + #[serde(rename = "inputIndex")] + pub input_index: usize, + pub sni: Option, +} + #[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] pub enum GraphWireStyle { #[default] @@ -56,7 +62,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, DVec2) { +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 => { diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 087b3df85b..c2a25d0758 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -394,14 +394,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ node: graphene_std::transform_nodes::transform::IDENTIFIER, aliases: &["graphene_core::transform::TransformNode"], }, - NodeReplacement { - node: graphene_std::transform_nodes::boundless_footprint::IDENTIFIER, - aliases: &["graphene_core::transform::BoundlessFootprintNode"], - }, - NodeReplacement { - node: graphene_std::transform_nodes::freeze_real_time::IDENTIFIER, - aliases: &["graphene_core::transform::FreezeRealTimeNode"], - }, // ??? NodeReplacement { node: graphene_std::vector::spline::IDENTIFIER, diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 86558d0177..e0f1f48e3f 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -9,7 +9,7 @@ use graphene_std::Color; use graphene_std::raster::Image; use graphene_std::renderer::RenderMetadata; use graphene_std::text::Font; -use graphene_std::uuid::{SNI}; +use graphene_std::uuid::SNI; #[impl_message(Message, Portfolio)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -22,17 +22,18 @@ 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. - EvaluateActiveDocument, - // Sends a request to introspect data in the network, and return it to the editor - IntrospectActiveDocument { + // Nodes to introspect is in addition to all nodes to render thumbnails for + EvaluateActiveDocumentWithThumbnails, + EvaluateActiveDocument { nodes_to_introspect: HashSet, }, + // Sends a request to introspect data in the network, and return it to the editor + // IntrospectActiveDocument { + // nodes_to_introspect: HashSet, + // }, ExportActiveDocument { file_name: String, file_type: FileType, @@ -47,12 +48,11 @@ pub enum PortfolioMessage { }, ProcessEvaluationResponse { evaluation_metadata: RenderMetadata, - }, - ProcessIntrospectionResponse { #[serde(skip)] - introspection_response: IntrospectionResponse, + introspected_nodes: IntrospectionResponse, }, - RenderThumbnails, + // Introspected data is cleared after queued messages are complete + ClearIntrospectedData, ProcessThumbnails, DocumentPassMessage { document_id: DocumentId, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 350e9b87b0..738fc0f6da 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -12,6 +12,7 @@ use crate::messages::portfolio::document::DocumentMessageContext; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; 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::utility_types::wires::{GraphWireStyle, WireSNIUpdate}; use crate::messages::portfolio::document_migration::*; use crate::messages::portfolio::spreadsheet::SpreadsheetMessageHandlerData; use crate::messages::preferences::SelectionMode; @@ -21,8 +22,9 @@ use crate::node_graph_executor::{CompilationRequest, ExportConfig, NodeGraphExec use glam::{DAffine2, DVec2}; use graph_craft::document::value::EditorMetadata; use graph_craft::document::{InputConnector, NodeInput, OutputConnector}; -use graphene_std::any::EditorContext; +use graphene_std::EditorContext; use graphene_std::application_io::TimingInformation; +use graphene_std::memo::MonitorIntrospectResult; use graphene_std::renderer::{Quad, RenderMetadata}; use graphene_std::text::Font; use graphene_std::transform::{Footprint, RenderQuality}; @@ -57,10 +59,10 @@ pub struct PortfolioMessageHandler { device_pixel_ratio: Option, pub reset_node_definitions_on_open: bool, // Data from the node graph, which is populated after an introspection request. - // To access the data, schedule messages with StartIntrospectionQueue [messages] EndIntrospectionQueue - // The data is no longer accessible after EndIntrospectionQueue - pub introspected_data: HashMap>>, - pub previous_thumbnail_data: HashMap>, + // To access the data, schedule messages with StartEvaluationQueue [messages] EndEvaluationQueue + // The data is no longer accessible after EndEvaluationQueue + pub introspected_data: HashMap, + thumbnails_to_clear: Vec, } #[message_handler_data] @@ -791,8 +793,6 @@ impl MessageHandler> for Portfolio transform_to_viewport: true, }, }); - // Also evaluate the document after compilation - responses.add_front(PortfolioMessage::EvaluateActiveDocument); } } PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => { @@ -814,67 +814,23 @@ impl MessageHandler> for Portfolio // Remove all thumbnails cleared_thumbnails.push(sni); } - responses.add(FrontendMessage::UpdateThumbnails { - add: Vec::new(), - clear: cleared_thumbnails, - }); + + self.thumbnails_to_clear.extend(cleared_thumbnails); } - PortfolioMessage::EvaluateActiveDocument => { + PortfolioMessage::EvaluateActiveDocumentWithThumbnails => { + responses.add(PortfolioMessage::EvaluateActiveDocument { + nodes_to_introspect: self.nodes_to_try_render(ipp.viewport_bounds()[1], &preferences.graph_wire_style), + }); + responses.add(Message::StartEvaluationQueue); + responses.add(PortfolioMessage::ProcessThumbnails); + responses.add(Message::EndEvaluationQueue); + } + PortfolioMessage::EvaluateActiveDocument { nodes_to_introspect } => { let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else { log::error!("Tried to render non-existent document: {:?}", self.active_document_id); return; }; - // Get all the inputs to save data for. This includes vector modify, thumbnails, and spreadsheet data - - // 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(&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, - // }; - // 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., // AnimationState::Playing { start } => ipp.time - start, @@ -891,9 +847,18 @@ impl MessageHandler> for Portfolio context.real_time = Some(ipp.time); context.downstream_transform = Some(DAffine2::IDENTITY); - self.executor.submit_node_graph_evaluation(context, None, None); + let nodes_to_try_render = self.nodes_to_try_render(ipp.viewport_bounds()[1], &preferences.graph_wire_style); + + self.executor.submit_node_graph_evaluation(context, None, None, nodes_to_try_render); } - PortfolioMessage::ProcessEvaluationResponse { evaluation_metadata } => { + PortfolioMessage::ProcessEvaluationResponse { + evaluation_metadata, + introspected_nodes, + } => { + for (protonode, data) in introspected_nodes.0.into_iter() { + self.introspected_data.insert(protonode, data); + } + let RenderMetadata { upstream_footprints: footprints, local_transforms, @@ -915,104 +880,71 @@ impl MessageHandler> for Portfolio // AnimationState::Playing { .. } => responses.add(PortfolioMessage::EvaluateActiveDocument), // _ => {} // }; - - // After an evaluation, always render all thumbnails - responses.add(PortfolioMessage::RenderThumbnails); - } - PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => { - self.executor.submit_node_graph_introspection(nodes_to_introspect); - } - PortfolioMessage::ProcessIntrospectionResponse { introspection_response } => { - for (protonode, data) in introspection_response.0.into_iter() { - self.introspected_data.insert(protonode, data); - } } + // PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => { + // self.executor.submit_node_graph_introspection(nodes_to_introspect); + // } + // PortfolioMessage::RenderThumbnails => { + // let nodes_to_render = self.nodes_to_render(); + // responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect: nodes_to_render }); + // responses.add(Message::StartIntrospectionQueue); + // responses.add(PortfolioMessage::ProcessThumbnails); + // responses.add(Message::EndIntrospectionQueue); + // } PortfolioMessage::ClearIntrospectedData => self.introspected_data.clear(), - PortfolioMessage::RenderThumbnails => { + PortfolioMessage::ProcessThumbnails => { + let mut thumbnail_response = ThumbnailRenderResponse::default(); + + for (thumbnail_node, monitor_result) in self.introspected_data.drain() { + let evaluated_data = match monitor_result { + MonitorIntrospectResult::Error => continue, + MonitorIntrospectResult::Disabled => continue, + MonitorIntrospectResult::NotEvaluated => continue, + MonitorIntrospectResult::Evaluated((data, changed)) => { + // If the evaluated value is the same as the previous, then just remap the ID + if !changed { + continue; + } + data + } + }; + match graph_craft::document::value::render_thumbnail(&evaluated_data) { + Some(thumbnail) => thumbnail_response.add.push((thumbnail_node, thumbnail)), + None => thumbnail_response.clear.push(thumbnail_node), + } + } 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; }; - // All possible inputs, later check if they are connected to any nodes - let mut nodes_to_render = HashSet::new(); - - // Get all inputs to render thumbnails for - // Get all protonodes for all connected side layer inputs connected to the export in the document network - for layer in document.network_interface.document_metadata().all_layers() { - let connector = InputConnector::Node { - node_id: layer.to_node(), + let mut wire_sni_updates = document + .network_interface + .document_metadata() + .all_layers() + .map(|layer| WireSNIUpdate { + id: layer.to_node(), input_index: 1, - }; - if document.network_interface.input_from_connector(&connector, &[]).is_some_and(|input| input.is_wire()) { - if let Some(compiled_input) = document.network_interface.protonode_from_input(&connector, &[]) { - nodes_to_render.insert(compiled_input); - } - } - } + sni: document.network_interface.protonode_from_input(&InputConnector::node(layer.to_node(), 1), &[]), + }) + .collect::>(); + let mut layer_sni_updates = Vec::new(); - // Save data for all inputs in the viewed node graph + // Only update wires/nodes in the node graph if they are open, but this will require syncing when the node graph resent. if document.graph_view_overlay_open { - let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else { - return; - }; - let mut wire_stack = viewed_network - .exports - .iter() - .enumerate() - .filter_map(|(export_index, export)| export.is_wire().then_some(InputConnector::Export(export_index))) - .collect::>(); - while let Some(input_connector) = wire_stack.pop() { - let Some(input) = document.network_interface.input_from_connector(&input_connector, &document.breadcrumb_network_path) else { - log::error!("Could not get input from connector: {:?}", input_connector); - continue; - }; - if let NodeInput::Node { node_id, .. } = input { - let Some(node) = document.network_interface.document_node(node_id, &document.breadcrumb_network_path) else { - log::error!("Could not get node"); - continue; - }; - for (wire_input_index, _) in node.inputs.iter().enumerate().filter(|(_, input)| input.is_wire()) { - wire_stack.push(InputConnector::Node { - node_id: *node_id, - input_index: wire_input_index, - }) - } - }; - let Some(protonode) = document.network_interface.protonode_from_input(&input_connector, &document.breadcrumb_network_path) else { - // The protonode has not been compiled, so it is not connected to the export - wire_stack = Vec::new(); - continue; - }; - nodes_to_render.insert(protonode); - } - }; - - responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect: nodes_to_render }); - responses.add(Message::StartIntrospectionQueue); - responses.add(PortfolioMessage::ProcessThumbnails); - responses.add(Message::EndIntrospectionQueue); - } - PortfolioMessage::ProcessThumbnails => { - let mut thumbnail_response = ThumbnailRenderResponse::default(); - for (thumbnail_node, introspected_data) in self.introspected_data.drain() { - 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_node); - - 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(thumbnail_node), - graph_craft::document::value::ThumbnailRenderResult::UpdateThumbnail(thumbnail) => thumbnail_response.add.push((thumbnail_node, thumbnail)), - } - self.previous_thumbnail_data.insert(thumbnail_node, evaluated_data); + let (layer_updates, wire_updates) = NodeGraphMessageHandler::graph_sni_updates(&document.network_interface, &document.breadcrumb_network_path); + layer_sni_updates.extend(layer_updates); + wire_sni_updates.extend(wire_updates); } + + let mut clear = std::mem::take(&mut self.thumbnails_to_clear); + clear.extend(thumbnail_response.clear); + responses.add(FrontendMessage::UpdateThumbnails { add: thumbnail_response.add, - clear: thumbnail_response.clear, - }) + clear, + wire_sni_updates, + layer_sni_updates, + }); } PortfolioMessage::ExportActiveDocument { file_name, @@ -1081,6 +1013,7 @@ impl MessageHandler> for Portfolio transparent_background, size: scaled_size, }), + HashSet::new(), ); // if let Some((start, end, fps)) = animation_export_data { @@ -1300,11 +1233,100 @@ 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::ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata::default())); + responses.add(Message::ProcessEvaluationQueue( + graphene_std::renderer::RenderMetadata::default(), + crate::node_graph_executor::IntrospectionResponse(Vec::new()), + )); responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error }); } result } + + pub fn nodes_to_try_render(&mut self, viewport_size: DVec2, graph_wire_style: &GraphWireStyle) -> HashSet { + 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 HashSet::new(); + }; + // All possible inputs, later check if they are connected to any nodes + let mut nodes_to_render = HashSet::new(); + + if document.graph_view_overlay_open { + let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else { + return HashSet::new(); + }; + let mut wire_stack = viewed_network + .exports + .iter() + .enumerate() + .filter_map(|(export_index, export)| export.is_wire().then_some(InputConnector::Export(export_index))) + .collect::>(); + while let Some(input_connector) = wire_stack.pop() { + let Some(input) = document.network_interface.input_from_connector(&input_connector, &document.breadcrumb_network_path) else { + log::error!("Could not get input from connector: {:?}", input_connector); + continue; + }; + if let NodeInput::Node { node_id, .. } = input { + let Some(node) = document.network_interface.document_node(node_id, &document.breadcrumb_network_path) else { + log::error!("Could not get node"); + continue; + }; + for (wire_input_index, _) in node.inputs.iter().enumerate().filter(|(_, input)| input.is_wire()) { + wire_stack.push(InputConnector::Node { + node_id: *node_id, + input_index: wire_input_index, + }) + } + }; + + // Save data for all thin wires with visible thumbnails in the viewed node graph + if let Some(viewport_position) = document + .network_interface + .viewport_loaded_thumbnail_position(&input_connector, graph_wire_style, &document.breadcrumb_network_path) + { + log::debug!("viewport position: {:?}, input: {:?}", viewport_position, input_connector); + let in_view = viewport_position.x < 0.0 || viewport_position.y < 0.0 || viewport_position.x > viewport_size.x || viewport_position.y > viewport_size.y; + if in_view { + let Some(protonode) = document.network_interface.protonode_from_input(&input_connector, &document.breadcrumb_network_path) else { + // The input is not connected to the export, which occurs if inside a disconnected node + wire_stack = Vec::new(); + nodes_to_render.clear(); + continue; + }; + nodes_to_render.insert(protonode); + } + } + } + }; + + // Get thumbnails for all visible layer + for visible_node in &document.node_graph_handler.frontend_nodes { + if document.network_interface.is_layer(&visible_node, &document.breadcrumb_network_path) { + log::debug!("visible_node: {:?}", visible_node); + let Some(protonode) = document + .network_interface + .protonode_from_output(&OutputConnector::node(*visible_node, 1), &document.breadcrumb_network_path) + else { + continue; + }; + nodes_to_render.insert(protonode); + } + } + + // Get all protonodes for all connected side layer inputs connected to the export in the document network + for layer in document.network_interface.document_metadata().all_layers() { + let connector = InputConnector::Node { + node_id: layer.to_node(), + input_index: 1, + }; + if document.network_interface.input_from_connector(&connector, &[]).is_some_and(|input| input.is_wire()) { + if let Some(protonode) = document.network_interface.protonode_from_input(&connector, &[]) { + nodes_to_render.insert(protonode); + } + } + } + + nodes_to_render + } } #[derive(Clone, Debug, Default)] diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index 32f3e1e233..95985b30c5 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -7,6 +7,7 @@ use graph_craft::document::OutputConnector; use graphene_std::Color; use graphene_std::GraphicGroupTable; use graphene_std::instances::Instances; +use graphene_std::memo::MonitorIntrospectResult; use graphene_std::raster::Image; use graphene_std::uuid::{NodeId, SNI}; use graphene_std::vector::{VectorData, VectorDataTable}; @@ -15,7 +16,7 @@ use std::sync::Arc; #[derive(ExtractField)] pub struct SpreadsheetMessageHandlerData<'a> { - pub introspected_data: &'a HashMap>>, + pub introspected_data: &'a HashMap, // Network interface of the selected document pub network_interface: &'a NodeNetworkInterface, } @@ -26,7 +27,7 @@ pub struct SpreadsheetMessageHandler { /// Sets whether or not the spreadsheet is drawn. pub spreadsheet_view_open: bool, // Path to the document node that is introspected. The protonode is found by traversing from the primary output - inspection_data: Option>>, + inspection_data: Option, node_to_inspect: Option, instances_path: Vec, @@ -73,10 +74,10 @@ impl MessageHandler> for S let mut nodes_to_introspect = HashSet::new(); nodes_to_introspect.insert(protonode_id); - responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect }); - responses.add(Message::StartIntrospectionQueue); + responses.add(PortfolioMessage::EvaluateActiveDocument { nodes_to_introspect }); + responses.add(Message::StartEvaluationQueue); responses.add(SpreadsheetMessage::ProcessUpdateLayout { node_to_inspect, protonode_id }); - responses.add(Message::EndIntrospectionQueue); + responses.add(Message::EndEvaluationQueue); self.update_layout(responses); } @@ -93,7 +94,6 @@ impl MessageHandler> for S self.instances_path.truncate(len); self.update_layout(responses); } - SpreadsheetMessage::ViewVectorDataDomain { domain } => { self.viewing_vector_data_domain = domain; self.update_layout(responses); @@ -126,11 +126,13 @@ impl SpreadsheetMessageHandler { Some(_) => { match &self.inspection_data { Some(data) => match data { - Some(inspected_data) => match generate_layout(&inspected_data, &mut layout_data) { + MonitorIntrospectResult::Error => label("The introspected node is a type that cannot be cloned"), + MonitorIntrospectResult::Disabled => label("Error: The introspected node must be set to StoreFirstEvaluation before introspection"), + MonitorIntrospectResult::NotEvaluated => label("Introspected data is not available for this input. This input may be cached."), + MonitorIntrospectResult::Evaluated((data, _)) => match generate_layout(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."), }, // There should always be an entry for each protonode input. If its empty then it was not requested or an error occured None => label("The output of this node could not be determined"), diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index aa2d11daa7..af8775eef8 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -82,8 +82,8 @@ impl MessageHandler for PreferencesMessageHandler { } PreferencesMessage::GraphWireStyle { style } => { self.graph_wire_style = style; - responses.add(NodeGraphMessage::UnloadWires); - responses.add(NodeGraphMessage::SendWires); + responses.add(NodeGraphMessage::UnloadWirePaths); + responses.add(NodeGraphMessage::SendWirePaths); } PreferencesMessage::ViewportZoomWheelRate { rate } => { self.viewport_zoom_wheel_rate = rate; diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 3eaeaa1aee..d4515d2dad 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -6,7 +6,8 @@ use dyn_any::DynAny; use graph_craft::document::value::{EditorMetadata, RenderOutput, TaggedValue}; use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid}; use graph_craft::proto::GraphErrors; -use graphene_std::any::EditorContext; +use graphene_std::EditorContext; +use graphene_std::memo::MonitorIntrospectResult; use graphene_std::renderer::format_transform_matrix; use graphene_std::text::FontCache; use graphene_std::uuid::SNI; @@ -30,6 +31,11 @@ pub struct CompilationResponse { node_graph_errors: GraphErrors, } +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct CacheEnableRequest { + nodes_to_enable: HashSet, +} + // Metadata the editor sends when evaluating the network #[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)] pub struct EvaluationRequest { @@ -37,16 +43,18 @@ pub struct EvaluationRequest { #[serde(skip)] pub context: EditorContext, pub node_to_evaluate: Option, + pub nodes_to_introspect: HashSet, } // #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] pub struct EvaluationResponse { evaluation_id: u64, result: Result, + introspected_nodes: IntrospectionResponse, } #[derive(Debug, Clone, Default)] -pub struct IntrospectionResponse(pub Vec<(SNI, Option>)>); +pub struct IntrospectionResponse(pub Vec<(SNI, MonitorIntrospectResult)>); impl PartialEq for IntrospectionResponse { fn eq(&self, _other: &Self) -> bool { @@ -58,7 +66,6 @@ impl PartialEq for IntrospectionResponse { pub enum NodeGraphUpdate { CompilationResponse(CompilationResponse), EvaluationResponse(EvaluationResponse), - IntrospectionResponse(IntrospectionResponse), } #[derive(Debug, Default)] @@ -83,18 +90,18 @@ impl Default for NodeGraphExecutor { impl NodeGraphExecutor { /// A local runtime is useful on threads since having global state causes flakes - #[cfg(test)] - pub(crate) fn new_with_local_runtime() -> (NodeRuntime, Self) { - let (request_sender, request_receiver) = std::sync::mpsc::channel(); - let (response_sender, response_receiver) = std::sync::mpsc::channel(); - let node_runtime = NodeRuntime::new(request_receiver, response_sender); + // #[cfg(test)] + // pub(crate) fn new_with_local_runtime() -> (NodeRuntime, Self) { + // let (request_sender, request_receiver) = std::sync::mpsc::channel(); + // let (response_sender, response_receiver) = std::sync::mpsc::channel(); + // let node_runtime = NodeRuntime::new(request_receiver, response_sender); - let node_executor = Self { - futures: HashMap::new(), - runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver), - }; - (node_runtime, node_executor) - } + // let node_executor = Self { + // futures: HashMap::new(), + // runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver), + // }; + // (node_runtime, node_executor) + // } /// Updates the network to monitor all inputs. Useful for the testing. // #[cfg(test)] @@ -110,19 +117,28 @@ impl NodeGraphExecutor { /// Compile the network pub fn submit_node_graph_compilation(&mut self, compilation_request: CompilationRequest) { - if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::CompilationRequest(compilation_request)) { + if let Err(error) = self.runtime_io.try_send(GraphRuntimeRequest::CompilationRequest(compilation_request)) { log::error!("Could not send evaluation request. {:?}", error); return; } } + // // Adds a request to set disabled cache nodes (automatically placed on the output of each node) to save the value of their first exeuction + // pub fn submit_node_graph_cache_enable(&mut self, nodes_to_enable: HashSet) { + // if let Err(error) = self.runtime_io.try_send(GraphRuntimeRequest::CacheEnableRequest(CacheEnableRequest { nodes_to_enable })) { + // log::error!("Could not send evaluation request. {:?}", error); + // return; + // } + // } + /// 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) { + pub fn submit_node_graph_evaluation(&mut self, context: EditorContext, node_to_evaluate: Option, export_config: Option, nodes_to_introspect: HashSet) { let evaluation_id = generate_uuid(); - if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::EvaluationRequest(EvaluationRequest { + if let Err(error) = self.runtime_io.try_send(GraphRuntimeRequest::EvaluationRequest(EvaluationRequest { evaluation_id, context, node_to_evaluate, + nodes_to_introspect, })) { log::error!("Could not send evaluation request. {:?}", error); return; @@ -131,18 +147,43 @@ impl NodeGraphExecutor { 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; - } - } + // pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet) { + // if let Err(error) = self.runtime_io.try_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> { - for response in self.runtime_io.receive() { + if let Ok(response) = self.runtime_io.receive() { + self.runtime_io.busy = false; match response { - NodeGraphUpdate::EvaluationResponse(EvaluationResponse { evaluation_id, result }) => { + NodeGraphUpdate::CompilationResponse(compilation_response) => { + let CompilationResponse { node_graph_errors, result } = compilation_response; + let compilation_metadata = match result { + Err(e) => { + // Clear the click targets while the graph is in an un-renderable state + document.network_interface.update_click_targets(HashMap::new()); + document.network_interface.update_vector_modify(HashMap::new()); + + document.node_graph_handler.node_graph_errors = node_graph_errors; + responses.add(NodeGraphMessage::SendGraph); + + log::trace!("{e}"); + return Err(format!("Node graph evaluation failed:\n{e}")); + } + Ok(result) => result, + }; + // Always evaluate after a compilation + responses.add_front(PortfolioMessage::EvaluateActiveDocumentWithThumbnails); + responses.add_front(PortfolioMessage::ProcessCompilationResponse { compilation_metadata }); + } + NodeGraphUpdate::EvaluationResponse(EvaluationResponse { + evaluation_id, + result, + introspected_nodes, + }) => { responses.add(OverlaysMessage::Draw); let node_graph_output = match result { @@ -186,36 +227,17 @@ impl NodeGraphExecutor { } } else { // Update artwork - self.process_node_graph_output(render_output, responses)? + self.process_node_graph_output(render_output, introspected_nodes, responses)? } - } - NodeGraphUpdate::CompilationResponse(compilation_response) => { - let CompilationResponse { node_graph_errors, result } = compilation_response; - let compilation_metadata = match result { - Err(e) => { - // Clear the click targets while the graph is in an un-renderable state - document.network_interface.update_click_targets(HashMap::new()); - document.network_interface.update_vector_modify(HashMap::new()); - - document.node_graph_handler.node_graph_errors = node_graph_errors; - responses.add(NodeGraphMessage::SendGraph); - - log::trace!("{e}"); - return Err(format!("Node graph evaluation failed:\n{e}")); - } - Ok(result) => result, - }; - responses.add(PortfolioMessage::ProcessCompilationResponse { compilation_metadata }); - } - NodeGraphUpdate::IntrospectionResponse(introspection_response) => { - responses.add(Message::ProcessIntrospectionQueue(introspection_response)); - } + } // NodeGraphUpdate::IntrospectionResponse(introspection_response) => { + // responses.add_front(Message::ProcessIntrospectionQueue(introspection_response)); + // } } } Ok(()) } - fn process_node_graph_output(&self, render_output: RenderOutput, responses: &mut VecDeque) -> Result<(), String> { + fn process_node_graph_output(&self, render_output: RenderOutput, introspected_nodes: IntrospectionResponse, responses: &mut VecDeque) -> Result<(), String> { match render_output.data { graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => { // Send to frontend @@ -234,7 +256,16 @@ impl NodeGraphExecutor { return Err(format!("Invalid node graph output type: {:#?}", render_output.data)); } } - responses.add(Message::ProcessEvaluationQueue(render_output.metadata)); + let context_during_evaluation = self + .runtime_io + .context_receiver + .try_iter() + .map(|(ids, index, context)| (ids, index, format!("{:?}", context))) + .collect::>(); + if context_during_evaluation.len() != 0 { + responses.add_front(FrontendMessage::UpdateContextDuringEvaluation { context_during_evaluation }); + } + responses.add_front(Message::ProcessEvaluationQueue(render_output.metadata, introspected_nodes)); Ok(()) } } diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index e44df76f21..ef26b940ed 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -24,6 +24,7 @@ pub struct NodeRuntime { executor: DynamicExecutor, receiver: Receiver, sender: NodeGraphRuntimeSender, + context_sender: Sender<(SNI, usize, EditorContext)>, application_io: Option>, @@ -46,7 +47,6 @@ pub enum GraphRuntimeRequest { // 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), } #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -69,19 +69,20 @@ 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") - } + // 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)); impl NodeRuntime { - pub fn new(receiver: Receiver, sender: Sender) -> Self { + pub fn new(receiver: Receiver, sender: Sender, context_sender: Sender<(SNI, usize, EditorContext)>) -> Self { Self { executor: DynamicExecutor::default(), receiver, sender: NodeGraphRuntimeSender(sender.clone()), + context_sender, application_io: None, @@ -100,24 +101,26 @@ impl NodeRuntime { } // TODO: This deduplication of messages may 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(_) => compilation = Some(request), - GraphRuntimeRequest::EvaluationRequest(_) => evaluation = Some(request), - GraphRuntimeRequest::IntrospectionRequest(_) => introspection = Some(request), - } - } - let requests = [compilation, evaluation, introspection].into_iter().flatten(); + // let mut compilation = None; + // let mut cache_enable = None; + // let mut evaluation = None; + // let mut introspection = None; + // for request in self.receiver.try_iter() { + // match request { + // GraphRuntimeRequest::CompilationRequest(_) => compilation = Some(request), + // GraphRuntimeRequest::CacheEnableRequest(_) => cache_enable = Some(request), + // GraphRuntimeRequest::EvaluationRequest(_) => evaluation = Some(request), + // GraphRuntimeRequest::IntrospectionRequest(_) => introspection = Some(request), + // } + // } - for request in requests { + // let requests = [compilation, cache_enable, evaluation, introspection].into_iter().flatten(); + + if let Ok(request) = self.receiver.try_recv() { 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, font_cache, editor_metadata).await; self.sender.send_compilation_response(CompilationResponse { @@ -125,25 +128,20 @@ impl NodeRuntime { 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, node_to_evaluate, + nodes_to_introspect, }) => { - // for (protonode_input, introspect_mode) in &inputs_to_monitor { - // self.executor.set_introspect(*protonode_input, *introspect_mode) - // } + for node in &nodes_to_introspect { + self.executor.cache_first_evaluation(node) + } let result = self.executor.evaluate_from_node(context, node_to_evaluate).await; - self.sender.send_evaluation_response(EvaluationResponse { evaluation_id, result }); - } - // GraphRuntimeRequest::ThumbnailRenderRequest(_) => {} - GraphRuntimeRequest::IntrospectionRequest(nodes) => { let mut introspected_nodes = Vec::new(); - for protonode in nodes { - let introspected_data = match self.executor.introspect(protonode, true) { + for protonode in nodes_to_introspect { + let introspected_data = match self.executor.introspect(protonode) { Ok(introspected_data) => introspected_data, Err(e) => { log::error!("Could not introspect protonode: {:?}, error: {:?}", protonode, e); @@ -153,7 +151,11 @@ impl NodeRuntime { introspected_nodes.push((protonode, introspected_data)); } - self.sender.send_introspection_response(IntrospectionResponse(introspected_nodes)); + self.sender.send_evaluation_response(EvaluationResponse { + evaluation_id, + result, + introspected_nodes: IntrospectionResponse(introspected_nodes), + }); } } } @@ -170,15 +172,14 @@ impl NodeRuntime { // 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, original_locations) = match scoped_network.flatten() { + let (proto_network, original_locations) = match scoped_network.compile() { Ok(result) => result, Err(e) => { log::error!("Error compiling network: {e:?}"); return Err(e); } }; - - let result = match self.executor.update(proto_network).await { + let result: Result = match self.executor.update(proto_network, None).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 diff --git a/editor/src/node_graph_executor/runtime_io.rs b/editor/src/node_graph_executor/runtime_io.rs index e4e6f1df40..88c3939c6a 100644 --- a/editor/src/node_graph_executor/runtime_io.rs +++ b/editor/src/node_graph_executor/runtime_io.rs @@ -1,4 +1,5 @@ use super::*; +use std::sync::mpsc::TryRecvError; use std::sync::mpsc::{Receiver, Sender}; use wasm_bindgen::prelude::*; @@ -14,12 +15,13 @@ extern "C" { /// Handles communication with the NodeRuntime, either locally or via Tauri #[derive(Debug)] pub struct NodeRuntimeIO { - // Send to + pub busy: bool, #[cfg(any(not(feature = "tauri"), test))] sender: Sender, #[cfg(all(feature = "tauri", not(test)))] sender: Sender, receiver: Receiver, + pub context_receiver: Receiver<(SNI, usize, EditorContext)>, } impl Default for NodeRuntimeIO { @@ -35,11 +37,14 @@ impl NodeRuntimeIO { { let (response_sender, response_receiver) = std::sync::mpsc::channel(); let (request_sender, request_receiver) = std::sync::mpsc::channel(); - futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender))); + let (context_sender, context_receiver) = std::sync::mpsc::channel(); + futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender, context_sender))); Self { + busy: false, sender: request_sender, receiver: response_receiver, + context_receiver, } } @@ -49,19 +54,25 @@ impl NodeRuntimeIO { Self { sender: response_sender, receiver: response_receiver, + context_receiver, } } } - #[cfg(test)] - pub fn with_channels(sender: Sender, receiver: Receiver) -> Self { - Self { sender, receiver } - } + // #[cfg(test)] + // pub fn with_channels(sender: Sender, receiver: Receiver) -> Self { + // Self { sender, receiver } + // } /// Sends a message to the NodeRuntime - pub fn send(&self, message: GraphRuntimeRequest) -> Result<(), String> { + pub fn try_send(&mut self, message: GraphRuntimeRequest) -> Result<(), String> { #[cfg(any(not(feature = "tauri"), test))] { - self.sender.send(message).map_err(|e| e.to_string()) + if !self.busy { + self.busy = true; + self.sender.send(message).map_err(|e| e.to_string()) + } else { + Err("Executor busy".to_string()) + } } #[cfg(all(feature = "tauri", not(test)))] @@ -76,7 +87,7 @@ impl NodeRuntimeIO { } /// Receives any pending updates from the NodeRuntime - pub fn receive(&self) -> impl Iterator + use<'_> { + pub fn receive(&mut self) -> Result { // TODO: This introduces extra latency #[cfg(all(feature = "tauri", not(test)))] { @@ -90,7 +101,7 @@ impl NodeRuntimeIO { } }); } - self.receiver.try_iter() + self.receiver.try_recv() } } diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index 64c78a79be..d84f6130a8 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -79,6 +79,6 @@ fn runtime_message(message: String) -> Result<(), String> { return Err("Failed to deserialize message".into()); } }; - let response = NODE_RUNTIME_IO.lock().as_ref().unwrap().as_ref().unwrap().send(message); + let response = NODE_RUNTIME_IO.lock().as_ref().unwrap().as_ref().unwrap().try_send(message); response } diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 092a83fbac..2c3d5c4c8b 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -92,6 +92,12 @@ updateLayerInTree(targetId, targetLayer); }); + editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerDetails, (updateDocumentLayerDetails) => { + const targetLayer = updateDocumentLayerDetails.data; + const targetId = targetLayer.id; + + updateLayerInTree(targetId, targetLayer); + }); addEventListener("pointermove", clippingHover); addEventListener("keydown", clippingKeyPress); addEventListener("keyup", clippingKeyPress); @@ -540,8 +546,8 @@ {/if}
- {#if $nodeGraph.thumbnails.has(listing.entry.id)} - {@html $nodeGraph.thumbnails.get(listing.entry.id)} + {#if $nodeGraph.thumbnails.get($nodeGraph.wires.get(listing.entry.id)?.get(BigInt(1))?.sni) !== undefined} + {@html $nodeGraph.thumbnails.get($nodeGraph.wires.get(listing.entry.id)?.get(BigInt(1))?.sni)} {/if}
{#if listing.entry.name === "Artboard"} diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 96a49d72bd..2370ad7ea1 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -230,6 +230,22 @@ } return result; } + + function generateCheckerboardString() { + let rects = ""; + + for (let y = 0; y < 6; y++) { + for (let x = 0; x < 8; x++) { + const isDark = (x + y) % 2 === 0; + const fill = isDark ? "#cccccc" : "#ffffff"; + rects += ``; + } + } + + rects += ``; + + return rects; + }
{#each $nodeGraph.wires.values() as map} - {#each map.values() as { pathString, dataType, thick, dashed }} + {#each map.values() as { pathString, dataType, thick, dashed, center, sni }} {#if thick} {node.errors} {/if}
- {#if $nodeGraph.thumbnails.has(node.id)} - {@html $nodeGraph.thumbnails.get(node.id)} + {#if node.layerThumbnailSNI && $nodeGraph.thumbnails.has(node.layerThumbnailSNI)} + {@html $nodeGraph.thumbnails.get(node.layerThumbnailSNI)} {/if} {#if node.primaryOutput} @@ -613,8 +630,8 @@
{#each $nodeGraph.wires.values() as map} - {#each map.values() as { pathString, dataType, thick, dashed, center, monitorSni }} - {#if !thick} + {#each map.values() as { pathString, dataType, thick, dashed, center, sni }} + {#if !thick && pathString} - {/if} - - - {#if center && monitorSni} - - - {@html $nodeGraph.thumbnails.get(monitorSni)} - + {/if} {/each} {/each} {#if $nodeGraph.wirePathInProgress} {/if} + {#each $nodeGraph.wires.values() as map} + {#each map.values() as { pathString, center, sni, dataType, thick }} + {#if !thick && pathString && sni && center !== undefined} +
+ {#if sni && $nodeGraph.thumbnails.has(sni)} + {@html $nodeGraph.thumbnails.get(sni)} + {/if} +
+ {/if} + {/each} + {/each}
@@ -891,7 +920,6 @@ position: absolute; width: 100%; height: 100%; - svg { width: 100%; height: 100%; @@ -904,6 +932,23 @@ stroke-dasharray: var(--data-dasharray); } } + + .wire-thumbnail { + // background: var(--color-2-mildblack); + border: 1px solid var(--data-color-dim); + border-radius: 2px; + position: absolute; + left: var(--offset-left-px); + top: var(--offset-top-px); + box-sizing: border-box; + width: 24px; + height: 16px; + // background-color: white; + background-image: var(--color-transparent-checkered-background); + background-size: var(--color-transparent-checkered-background-size-mini); + background-position: var(--color-transparent-checkered-background-position-mini); + background-repeat: var(--color-transparent-checkered-background-repeat); + } } .imports-and-exports { diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 9295b642c8..3d0a459212 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -12,6 +12,7 @@ export class JsMessage { } const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] })); + const ImportsToVec2Array = Transform(({ obj: { imports } }: { obj: { imports: [FrontendGraphOutput, number, number][] } }) => imports.map(([outputMetadata, x, y]) => ({ outputMetadata, position: { x, y } })), ); @@ -36,6 +37,10 @@ export class UpdateBox extends JsMessage { readonly box!: Box | undefined; } +export class UpdateGraphBreadcrumbPath extends JsMessage { + readonly breadcrumbPath!: bigint[]; +} + export class UpdateClickTargets extends JsMessage { readonly clickTargets!: FrontendClickTargets | undefined; } @@ -57,6 +62,10 @@ export class UpdateContextMenuInformation extends JsMessage { readonly contextMenuInformation!: ContextMenuInformation | undefined; } +export class UpdateContextDuringEvaluation extends JsMessage { + readonly contextDuringEvaluation!: [bigint, number, string][]; +} + export class UpdateImportsExports extends JsMessage { @ImportsToVec2Array readonly imports!: { outputMetadata: FrontendGraphOutput; position: XY }[]; @@ -109,7 +118,7 @@ export class UpdateNodeGraphWires extends JsMessage { readonly wires!: WireUpdate[]; } -export class ClearAllNodeGraphWires extends JsMessage {} +export class ClearAllNodeGraphWirePaths extends JsMessage {} export class UpdateNodeGraphTransform extends JsMessage { readonly transform!: NodeGraphTransform; @@ -128,6 +137,10 @@ export class UpdateThumbnails extends JsMessage { readonly add!: [bigint, string][]; readonly clear!: bigint[]; + + readonly wireSNIUpdates!: WireSNIUpdate[]; + + readonly layerSNIUpdates!: FrontendLayerSNIUpdate[]; } export class UpdateNodeGraphSelection extends JsMessage { @@ -266,6 +279,8 @@ export class FrontendGraphOutput { export class FrontendNode { readonly isLayer!: boolean; + layerThumbnailSNI!: bigint | undefined; + readonly canBeLayer!: boolean; readonly id!: bigint; @@ -302,6 +317,12 @@ export class FrontendNode { readonly uiOnly!: boolean; } +export class FrontendLayerSNIUpdate { + readonly id!: bigint; + + readonly sni!: bigint | undefined; +} + export class FrontendNodeType { readonly name!: string; @@ -317,13 +338,12 @@ export class NodeGraphTransform { } export class WirePath { - readonly pathString!: string; + pathString!: string; readonly dataType!: FrontendGraphDataType; readonly thick!: boolean; readonly dashed!: boolean; - @TupleToVec2 - readonly center!: XY | undefined; - readonly inputSni!: bigint; + readonly center!: [number, number] | undefined; + sni!: bigint | undefined; } export class WireUpdate { @@ -332,6 +352,12 @@ export class WireUpdate { readonly wirePathUpdate!: WirePath | undefined; } +export class WireSNIUpdate { + readonly id!: bigint; + readonly inputIndex!: number; + readonly sni!: bigint | undefined; +} + export class IndexedDbDocumentDetails extends DocumentDetails { @Transform(({ value }: { value: bigint }) => value.toString()) id!: string; @@ -1624,7 +1650,7 @@ type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHand type MessageMaker = typeof JsMessage | JSMessageFactory; export const messageMakers: Record = { - ClearAllNodeGraphWires, + ClearAllNodeGraphWirePaths, DisplayDialog, DisplayDialogDismiss, DisplayDialogPanic, @@ -1653,8 +1679,10 @@ export const messageMakers: Record = { TriggerVisitLink, UpdateActiveDocument, UpdateBox, + UpdateGraphBreadcrumbPath, UpdateClickTargets, UpdateContextMenuInformation, + UpdateContextDuringEvaluation, UpdateDialogButtons, UpdateDialogColumn1, UpdateDialogColumn2, diff --git a/frontend/src/state-providers/node-graph.ts b/frontend/src/state-providers/node-graph.ts index e0e214ad33..69036be632 100644 --- a/frontend/src/state-providers/node-graph.ts +++ b/frontend/src/state-providers/node-graph.ts @@ -9,11 +9,13 @@ import { type FrontendNode, type FrontendNodeType, type WirePath, - ClearAllNodeGraphWires, + ClearAllNodeGraphWirePaths, SendUIMetadata, UpdateBox, + UpdateGraphBreadcrumbPath, UpdateClickTargets, UpdateContextMenuInformation, + UpdateContextDuringEvaluation, UpdateInSelectedNetwork, UpdateImportReorderIndex, UpdateExportReorderIndex, @@ -32,6 +34,7 @@ import { export function createNodeGraphState(editor: Editor) { const { subscribe, update } = writable({ box: undefined as Box | undefined, + breadcrumbPath: [] as bigint[], clickTargets: undefined as FrontendClickTargets | undefined, contextMenuInformation: undefined as ContextMenuInformation | undefined, layerWidths: new Map(), @@ -43,8 +46,9 @@ export function createNodeGraphState(editor: Editor) { addExport: undefined as { x: number; y: number } | undefined, nodes: new Map(), visibleNodes: new Set(), - /// The index is the exposed input index. The exports have a first key value of u32::MAX. + /// The first key is the document node id. The index is the actual input index. The exports have a first key value of u32::MAX. wires: new Map>(), + /// The first key is the caller stable node id wirePathInProgress: undefined as WirePath | undefined, nodeDescriptions: new Map(), nodeTypes: [] as FrontendNodeType[], @@ -70,6 +74,13 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); + editor.subscriptions.subscribeJsMessage(UpdateGraphBreadcrumbPath, (updateGraphBreadcrumbPath) => { + update((state) => { + state.breadcrumbPath = updateGraphBreadcrumbPath.breadcrumbPath; + return state; + }); + }); + editor.subscriptions.subscribeJsMessage(UpdateClickTargets, (UpdateClickTargets) => { update((state) => { state.clickTargets = UpdateClickTargets.clickTargets; @@ -82,6 +93,11 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); + // editor.subscriptions.subscribeJsMessage(UpdateContextDuringEvaluation, (updateContextDuringEvaluation) => { + // update((state) => { + // return state; + // }); + // }); editor.subscriptions.subscribeJsMessage(UpdateImportReorderIndex, (updateImportReorderIndex) => { update((state) => { state.reorderImportIndex = updateImportReorderIndex.importIndex; @@ -118,7 +134,6 @@ export function createNodeGraphState(editor: Editor) { }); }); editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => { - // console.log(updateNodeGraphNodes); update((state) => { state.nodes.clear(); updateNodeGraphNodes.nodes.forEach((node) => { @@ -127,6 +142,7 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); + editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => { update((state) => { state.visibleNodes = new Set(updateVisibleNodes.nodes); @@ -143,17 +159,33 @@ export function createNodeGraphState(editor: Editor) { state.wires.set(wireUpdate.id, inputMap); } if (wireUpdate.wirePathUpdate !== undefined) { - inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate); + const existing = inputMap.get(wireUpdate.inputIndex); + if (existing) { + inputMap.set(wireUpdate.inputIndex, { + ...wireUpdate.wirePathUpdate, + sni: existing.sni, + }); + } else { + inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate); + } } else { - inputMap.delete(wireUpdate.inputIndex); + const existing = inputMap.get(wireUpdate.inputIndex); + if (existing) { + existing.pathString = ""; + } } }); + return state; }); }); - editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWires, (_) => { + editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWirePaths, (_) => { update((state) => { - state.wires.clear(); + for (const [, innerMap] of state.wires) { + for (const [, wirePath] of innerMap) { + wirePath.pathString = ""; + } + } return state; }); }); @@ -178,7 +210,22 @@ export function createNodeGraphState(editor: Editor) { for (const id of updateThumbnails.clear) { state.thumbnails.set(id, ""); } - // console.log("thumbnails: ", state.thumbnails); + updateThumbnails.wireSNIUpdates.forEach((wireUpdate) => { + const inputMap = state.wires.get(wireUpdate.id); + if (inputMap) { + const wire = inputMap.get(wireUpdate.inputIndex); + if (wire) { + wire.sni = wireUpdate.sni; + } + } + }); + updateThumbnails.layerSNIUpdates.forEach((wireUpdate) => { + const node = state.nodes.get(wireUpdate.id); + if (node) { + node.layerThumbnailSNI = wireUpdate.sni; + } + }); + return state; }); }); diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 1c0be3e561..3fd1ffc34d 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -1,7 +1,9 @@ +use dyn_any::StaticType; use glam::{DAffine2, UVec2}; use crate::transform::Footprint; use std::any::Any; +use std::fmt; use std::panic::Location; use std::sync::Arc; @@ -26,6 +28,14 @@ pub trait ExtractRealTime { fn try_real_time(&self) -> Option; } +pub trait ModifyDownstreamTransform: ExtractAll + CloneVarArgs { + fn apply_modification(self, modification: &DAffine2) -> Context; +} + +pub trait WithIndex: ExtractAll + CloneVarArgs { + fn with_index(&self, index: usize) -> Context; +} + pub trait ExtractAnimationTime { fn try_animation_time(&self) -> Option; } @@ -50,7 +60,7 @@ pub trait ExtractAll: ExtractFootprint + ExtractDownstreamTransform + ExtractInd impl ExtractAll for T {} -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] #[repr(u8)] pub enum ContextDependency { ExtractFootprint = 0b10000000, @@ -62,7 +72,7 @@ pub enum ContextDependency { ExtractVarArgs = 0b00000100, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub struct ContextDependencies(pub u8); impl ContextDependencies { @@ -99,6 +109,34 @@ impl ContextDependencies { } } +impl fmt::Debug for ContextDependencies { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut set = Vec::new(); + let bits = self.0; + + if bits & ContextDependency::ExtractFootprint as u8 != 0 { + set.push("ExtractFootprint"); + } + if bits & ContextDependency::ExtractDownstreamTransform as u8 != 0 { + set.push("ExtractDownstreamTransform"); + } + if bits & ContextDependency::ExtractRealTime as u8 != 0 { + set.push("ExtractRealTime"); + } + if bits & ContextDependency::ExtractAnimationTime as u8 != 0 { + set.push("ExtractAnimationTime"); + } + if bits & ContextDependency::ExtractIndex as u8 != 0 { + set.push("ExtractIndex"); + } + if bits & ContextDependency::ExtractVarArgs as u8 != 0 { + set.push("ExtractVarArgs"); + } + + f.debug_list().entries(set).finish() + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum VarArgsResult { IndexOutOfBounds, @@ -110,7 +148,7 @@ impl Ctx for () {} impl Ctx for Footprint {} impl ExtractFootprint for () { fn try_footprint(&self) -> Option<&Footprint> { - log::error!("tried to extract footprint form (), {}", Location::caller()); + log::error!("tried to extract footprint from (), {}", Location::caller()); None } } @@ -129,7 +167,7 @@ impl ExtractFootprint for Option { impl ExtractDownstreamTransform for () { fn try_downstream_transform(&self) -> Option<&DAffine2> { - log::error!("tried to extract downstream transform form (), {}", Location::caller()); + log::error!("tried to extract downstream transform from (), {}", Location::caller()); None } } @@ -146,6 +184,42 @@ impl ExtractDownstreamTransform for Option } } +impl ExtractDownstreamTransform for Arc { + fn try_downstream_transform(&self) -> Option<&DAffine2> { + (**self).try_downstream_transform() + } +} + +impl ExtractDownstreamTransform for OwnedContextImpl { + fn try_downstream_transform(&self) -> Option<&DAffine2> { + self.downstream_transform.as_ref() + } +} + +impl ModifyDownstreamTransform for Option { + fn apply_modification(self, modification: &DAffine2) -> Context { + if let Some(inner) = self { + let mut context = OwnedContextImpl::from(inner); + context.try_apply_downstream_transform(modification); + context.into_context() + } else { + None + } + } +} + +impl WithIndex for Option { + fn with_index(&self, index: usize) -> Context { + if let Some(inner) = self { + let mut context = OwnedContextImpl::from(inner.clone()); + context.set_index(index); + context.into_context() + } else { + None + } + } +} + impl ExtractRealTime for Option { fn try_real_time(&self) -> Option { self.as_ref().and_then(|x| x.try_real_time()) @@ -178,12 +252,6 @@ impl ExtractFootprint for Arc { } } -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() @@ -237,12 +305,6 @@ impl ExtractFootprint for OwnedContextImpl { } } -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 @@ -395,6 +457,16 @@ impl OwnedContextImpl { self.parent = None } } + + pub fn to_editor_context(&self) -> EditorContext { + EditorContext { + footprint: self.footprint, + downstream_transform: self.downstream_transform, + real_time: self.real_time, + animation_time: self.animation_time, + index: self.index, + } + } } impl OwnedContextImpl { @@ -404,9 +476,9 @@ impl OwnedContextImpl { pub fn set_downstream_transform(&mut self, transform: DAffine2) { self.downstream_transform = Some(transform); } - pub fn try_apply_downstream_transform(&mut self, transform: DAffine2) { + 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); + self.downstream_transform = Some(downstream_transform * *transform); } } pub fn set_real_time(&mut self, time: f64) { @@ -434,14 +506,10 @@ impl OwnedContextImpl { self.animation_time = Some(animation_time); self } - pub fn with_index(mut self, index: usize) -> Self { - if let Some(current_index) = &mut self.index { - current_index.push(index); - } else { - self.index = Some(vec![index]); - } - self - } + // pub fn with_index(mut self, index: usize) -> Self { + // self.index = Some(index); + // self + // } pub fn into_context(self) -> Option> { Some(Arc::new(self)) } @@ -465,6 +533,50 @@ impl OwnedContextImpl { } } +#[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 EditorContext { + pub fn to_owned_context(&self) -> OwnedContextImpl { + 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); + } + context + // if let Some(editor_var_args) = self.editor_var_args { + // let (variable_names, values) + // context.set_varargs((variable_names, values)) + // } + } +} + // #[derive(Default, Clone, Copy, dyn_any::DynAny)] // pub struct ContextImpl<'a> { // pub(crate) footprint: Option<&'a Footprint>, diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index af8ae31230..fd4bff82e7 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -4,10 +4,9 @@ use crate::instances::{Instance, Instances}; use crate::math::quad::Quad; use crate::raster::image::Image; use crate::raster_types::{CPU, GPU, Raster, RasterDataTable}; -use crate::transform::TransformMut; use crate::uuid::NodeId; use crate::vector::{VectorData, VectorDataTable}; -use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; +use crate::{ Color, Context, Ctx, ModifyDownstreamTransform}; use dyn_any::DynAny; use glam::{DAffine2, DVec2, IVec2}; use std::hash::Hash; @@ -457,7 +456,7 @@ async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTabl #[node_macro::node(category(""))] async fn to_artboard + 'n>( - ctx: impl ExtractAll + CloneVarArgs + Ctx, + ctx: impl Ctx + ModifyDownstreamTransform, #[implementations( Context -> GraphicGroupTable, Context -> VectorDataTable, @@ -471,13 +470,8 @@ async fn to_artboard + 'n>( background: Color, clip: bool, ) -> Artboard { - let footprint = ctx.try_footprint().copied(); - let mut new_ctx = OwnedContextImpl::from(ctx); - if let Some(mut footprint) = footprint { - footprint.translate(location.as_dvec2()); - new_ctx = new_ctx.with_footprint(footprint); - } - let graphic_group = contents.eval(new_ctx.into_context()).await; + let modified_ctx = ctx.apply_modification(&DAffine2::from_translation(location.as_dvec2())); + let graphic_group = contents.eval(modified_ctx).await; Artboard { graphic_group: graphic_group.into(), diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 42a92059d6..207bbe016e 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -32,6 +32,7 @@ pub mod value; pub mod vector; pub use crate as graphene_core; +use crate::memo::MonitorIntrospectResult; pub use blending::*; pub use context::*; pub use ctor; @@ -61,10 +62,14 @@ pub trait Node<'i, Input> { } // If check if evaluated is true, then it returns None if the node has not been evaluated since the last introspection - fn introspect(&self, _check_if_evaluated: bool) -> Option> { + fn introspect(&self) -> MonitorIntrospectResult { log::warn!("Node::introspect not implemented for {}", std::any::type_name::()); - None + MonitorIntrospectResult::Error } + + fn permanently_enable_cache(&self) {} + + fn cache_first_evaluation(&self) {} } mod types; diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index 983482a8a0..2530aeb110 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -7,14 +7,37 @@ use std::ops::Deref; use std::sync::Arc; use std::sync::Mutex; +#[derive(Debug)] +pub enum MonitorMemoNodeState { + Disabled, + // Stores the first execution, then gets set first execution result, which stores if the value changed + StoreFirstEvaluation, + // Gets set back to disabled on introspection, and stores a boolean for if the value changed since the last introspection + FirstEvaluationResult(bool), + // Acts as a normal cache node, and stores a boolean for if the value changed since the last introspection + Enabled(bool), +} + +#[derive(Clone, Debug)] +pub enum MonitorIntrospectResult { + // If trying to inspect a none that cannot be introspected + Error, + Disabled, + // The cache node has not been evaluated since the state was set to StoreFirstEvaluation/Enabled + NotEvaluated, + // If the monitor node was evaluated, then its data must exist, so it is not an option. + // The boolean represents if the data changed since the last introspection + Evaluated((std::sync::Arc, bool)), +} + /// 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>>>, cache: Arc)>>>, node: CachedNode, - changed_since_last_eval: Arc>, + hash_on_last_introspection: Arc>, + state: Arc>, } impl<'i, I: Hash + 'i + std::fmt::Debug, T: 'static + Clone + Send + Sync, CachedNode: 'i> Node<'i, I> for MonitorMemoNode where @@ -26,47 +49,110 @@ where 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(); + let mut state = self.state.lock().unwrap(); - 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 - }) + // log::debug!("Monitor memo node state: {:?}", *state); + + if matches!(*state, MonitorMemoNodeState::Disabled | MonitorMemoNodeState::FirstEvaluationResult(_)) { + return Box::pin(self.node.eval(input)); + } + + let hash = { + let mut hasher = DefaultHasher::new(); + input.hash(&mut hasher); + hasher.finish() + }; + + // log::debug!( + // "Monitor memo node input: {:?}, hash: {:?}, previous_hash: {:?}", + // input, + // hash, + // self.cache.lock().unwrap().as_ref().map(|(h, _)| *h) + // ); + + let last_hash = *self.hash_on_last_introspection.lock().unwrap(); + + match &mut *state { + MonitorMemoNodeState::Enabled(changed) => { + *changed = last_hash != hash; + } + MonitorMemoNodeState::StoreFirstEvaluation => { + *state = MonitorMemoNodeState::FirstEvaluationResult(last_hash != hash); + } + _ => {} + } + + let cache_guard = self.cache.lock().unwrap(); + + if let Some((cached_hash, cached_data)) = cache_guard.as_ref() { + if *cached_hash == hash { + let cloned_data = (**cached_data).clone(); + return Box::pin(async move { cloned_data }); + } + } + + drop(cache_guard); + + Box::pin(async move { + let value = self.node.eval(input).await; + *self.cache.lock().unwrap() = Some((hash, Arc::new(value.clone()))); + value + }) + } + + fn introspect(&self) -> MonitorIntrospectResult { + let mut state_guard = self.state.lock().unwrap(); + match *state_guard { + MonitorMemoNodeState::Disabled => { + // Make sure to set the state to "StoreFirstEvaluation" or "Enabled" before trying to introspect + log::error!("Cannot introspect disabled monitor memo node"); + MonitorIntrospectResult::Disabled + } + MonitorMemoNodeState::StoreFirstEvaluation => MonitorIntrospectResult::NotEvaluated, + MonitorMemoNodeState::FirstEvaluationResult(changed_since_last_introspection) => { + let (hash, cache_value) = self + .cache + .lock() + .unwrap() + .as_ref() + .map(|(hash, data)| (*hash, (*data).clone() as Arc)) + .expect("Evaluated cache node must store data"); + *self.hash_on_last_introspection.lock().unwrap() = hash; + *state_guard = MonitorMemoNodeState::Disabled; + MonitorIntrospectResult::Evaluated((cache_value, changed_since_last_introspection)) + } + MonitorMemoNodeState::Enabled(changed_since_last_introspection) => { + let cache = self.cache.lock().unwrap().as_ref().map(|(hash, data)| (*hash, (*data).clone() as Arc)); + match cache { + Some((hash, cache_value)) => { + *self.hash_on_last_introspection.lock().unwrap() = hash; + MonitorIntrospectResult::Evaluated((cache_value, changed_since_last_introspection)) + } + None => MonitorIntrospectResult::NotEvaluated, + } + } } } - // TODO: Consider returning a reference to the entire cache so the frontend reference is automatically updated as the context changes - fn introspect(&self, check_if_evaluated: bool) -> Option> { - let mut changed = self.changed_since_last_eval.lock().unwrap(); - if check_if_evaluated { - if !*changed { - return None; - } - } - *changed = false; + fn permanently_enable_cache(&self) { + *self.state.lock().unwrap() = MonitorMemoNodeState::Enabled(false); + } - let cache_guard = self.cache.lock().unwrap(); - let cached = cache_guard.as_ref().expect("Cached data should always be evaluated before introspection"); - Some(cached.1.clone() as Arc) + fn cache_first_evaluation(&self) { + if matches!(*self.state.lock().unwrap(), MonitorMemoNodeState::Enabled(_)) { + return; + } + *self.state.lock().unwrap() = MonitorMemoNodeState::StoreFirstEvaluation; } } impl MonitorMemoNode { - pub fn new(node: CachedNode) -> MonitorMemoNode { + pub fn new(node: CachedNode, state: MonitorMemoNodeState) -> MonitorMemoNode { MonitorMemoNode { cache: Default::default(), node, - changed_since_last_eval: Arc::new(Mutex::new(true)), + hash_on_last_introspection: Arc::new(Mutex::new(0)), + state: Arc::new(Mutex::new(state)), } } } diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 817f706965..71d9c5fcce 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -1,3 +1,4 @@ +use crate::memo::MonitorMemoNodeState; use crate::{ContextDependencies, Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend}; use dyn_any::{DynAny, StaticType}; use std::borrow::Cow; @@ -134,7 +135,7 @@ pub type TypeErasedPinned<'n> = Pin>>; pub type SharedNodeContainer = std::sync::Arc; pub type NodeConstructor = fn(Vec) -> DynFuture<'static, TypeErasedBox<'static>>; -pub type CacheConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>; +pub type CacheConstructor = fn(SharedNodeContainer, MonitorMemoNodeState) -> TypeErasedBox<'static>; #[derive(Clone)] pub struct NodeContainer { @@ -290,8 +291,16 @@ where } } - fn introspect(&self, check_if_evaluated: bool) -> Option> { - self.node.introspect(check_if_evaluated) + fn introspect(&self) -> crate::memo::MonitorIntrospectResult { + self.node.introspect() + } + + fn permanently_enable_cache(&self) { + self.node.permanently_enable_cache(); + } + + fn cache_first_evaluation(&self) { + self.node.cache_first_evaluation(); } fn reset(&self) { diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index dafd3791b7..552ab95b08 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -110,8 +110,10 @@ impl Footprint { quality: RenderQuality::Full, }; - pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox { - let inverse = self.transform.inverse(); + pub fn viewport_bounds_in_local_space(&self, downstream_transform: &DAffine2) -> AxisAlignedBbox { + // TODO: Check if this is the correct way to apply downstream transforms + let transform = self.transform * *downstream_transform; + let inverse = transform.inverse(); let start = inverse.transform_point2((0., 0.).into()); let end = inverse.transform_point2(self.resolution.as_dvec2()); AxisAlignedBbox { start, end } diff --git a/node-graph/gcore/src/transform_nodes.rs b/node-graph/gcore/src/transform_nodes.rs index 4cde6a7457..a20f9a5f25 100644 --- a/node-graph/gcore/src/transform_nodes.rs +++ b/node-graph/gcore/src/transform_nodes.rs @@ -1,14 +1,14 @@ use crate::instances::Instances; use crate::raster_types::{CPU, GPU, RasterDataTable}; -use crate::transform::{ApplyTransform, Footprint, Transform}; +use crate::transform::{Transform}; use crate::vector::VectorDataTable; -use crate::{CloneVarArgs, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl}; +use crate::{ Context, Ctx, GraphicGroupTable, ModifyDownstreamTransform, }; use core::f64; use glam::{DAffine2, DVec2}; #[node_macro::node(category(""))] async fn transform( - ctx: impl Ctx + CloneVarArgs + ExtractAll, + ctx: impl Ctx + ModifyDownstreamTransform, #[implementations( Context -> VectorDataTable, Context -> GraphicGroupTable, @@ -23,15 +23,9 @@ async fn transform( ) -> Instances { let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]); - let footprint = ctx.try_footprint().copied(); + let modified_ctx = ctx.apply_modification(&matrix); - let mut ctx = OwnedContextImpl::from(ctx); - if let Some(mut footprint) = footprint { - footprint.apply_transform(&matrix); - ctx = ctx.with_footprint(footprint); - } - - let mut transform_target = transform_target.eval(ctx.into_context()).await; + let mut transform_target = transform_target.eval(modified_ctx).await; for data_transform in transform_target.instance_mut_iter() { *data_transform.transform = matrix * *data_transform.transform; @@ -51,39 +45,3 @@ fn replace_transform( } data } - -#[node_macro::node(category("Debug"))] -async fn boundless_footprint( - ctx: impl Ctx + CloneVarArgs + ExtractAll, - #[implementations( - Context -> VectorDataTable, - Context -> GraphicGroupTable, - Context -> RasterDataTable, - Context -> RasterDataTable, - Context -> String, - Context -> f64, - )] - transform_target: impl Node, Output = T>, -) -> T { - let ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::BOUNDLESS); - - transform_target.eval(ctx.into_context()).await -} - -#[node_macro::node(category("Debug"))] -async fn freeze_real_time( - ctx: impl Ctx + CloneVarArgs + ExtractAll, - #[implementations( - Context -> VectorDataTable, - Context -> GraphicGroupTable, - Context -> RasterDataTable, - Context -> RasterDataTable, - Context -> String, - Context -> f64, - )] - transform_target: impl Node, Output = T>, -) -> T { - let ctx = OwnedContextImpl::from(ctx).with_real_time(0.); - - transform_target.eval(ctx.into_context()).await -} diff --git a/node-graph/gcore/src/vector/algorithms/instance.rs b/node-graph/gcore/src/vector/algorithms/instance.rs index 6530df7a90..1817b7ef2d 100644 --- a/node-graph/gcore/src/vector/algorithms/instance.rs +++ b/node-graph/gcore/src/vector/algorithms/instance.rs @@ -1,12 +1,12 @@ use crate::instances::{InstanceRef, Instances}; use crate::raster_types::{CPU, RasterDataTable}; use crate::vector::VectorDataTable; -use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl}; +use crate::{ Context, Ctx, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, WithIndex}; use glam::DVec2; #[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))] async fn instance_on_points + Default + Send + Clone + 'static>( - ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx, + ctx: impl Ctx + WithIndex, points: VectorDataTable, #[implementations( Context -> GraphicGroupTable, @@ -22,8 +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(("Transformed point", Box::new(transformed_point))); - let generated_instance = instance.eval(new_ctx.into_context()).await; + let generated_instance = instance.eval(ctx.with_index(index)).await; for mut instanced in generated_instance.instance_iter() { instanced.transform.translation = transformed_point; @@ -48,7 +47,7 @@ async fn instance_on_points + Default + Send + Clone + ' #[node_macro::node(category("Instancing"), path(graphene_core::vector))] async fn instance_repeat + Default + Send + Clone + 'static>( - ctx: impl ExtractAll + CloneVarArgs + Ctx, + ctx: impl Ctx + WithIndex, #[implementations( Context -> GraphicGroupTable, Context -> VectorDataTable, @@ -65,8 +64,7 @@ async fn instance_repeat + Default + Send + Clone + 'sta for index in 0..count { let index = if reverse { count - index - 1 } else { index }; - let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index); - let generated_instance = instance.eval(new_ctx.into_context()).await; + let generated_instance = instance.eval(ctx.with_index(index)).await; for instanced in generated_instance.instance_iter() { result_table.push(instanced); diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 513cdb5638..51c65d7dae 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -8,19 +8,14 @@ use crate::bounds::BoundingBox; use crate::instances::{Instance, InstanceMut, Instances}; use crate::raster_types::{CPU, GPU, RasterDataTable}; use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue}; -use crate::transform::{Footprint, ReferencePoint, Transform}; -use crate::vector::PointDomain; -use crate::vector::algorithms::bezpath_algorithms::{eval_pathseg_euclidean, is_linear}; +use crate::transform::{ReferencePoint, Transform}; use crate::vector::algorithms::merge_by_distance::MergeByDistanceExt; use crate::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType}; use crate::vector::misc::{handles_to_segment, segment_to_handles}; use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin}; -use crate::vector::{FillId, RegionId}; -use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl}; - -use bezier_rs::{BezierHandles, Join, ManipulatorGroup, Subpath}; -use core::f64::consts::PI; -use core::hash::{Hash, Hasher}; +use crate::vector::{FillId, PointDomain, RegionId}; +use crate::{Color, Ctx, GraphicElement, GraphicGroupTable}; +use bezier_rs::{Join, ManipulatorGroup, Subpath}; use glam::{DAffine2, DVec2}; use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Shape}; use rand::{Rng, SeedableRng}; @@ -2063,10 +2058,7 @@ async fn path_length(_: impl Ctx, source: VectorDataTable) -> f64 { } #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] -async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node, Output = VectorDataTable>) -> f64 { - let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); - let vector_data = vector_data.eval(new_ctx).await; - +async fn area(_ctx: impl Ctx, vector_data: VectorDataTable) -> f64 { vector_data .instance_ref_iter() .map(|vector_data_instance| { @@ -2077,10 +2069,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node< } #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] -async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 { - let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); - let vector_data = vector_data.eval(new_ctx).await; - +async fn centroid(_ctx: impl Ctx, vector_data: VectorDataTable, centroid_type: CentroidType) -> DVec2 { if vector_data.is_empty() { return DVec2::ZERO; } diff --git a/node-graph/graph-craft/benches/compile_demo_art_criterion.rs b/node-graph/graph-craft/benches/compile_demo_art_criterion.rs index 7c4c0fb79b..8c947a10ac 100644 --- a/node-graph/graph-craft/benches/compile_demo_art_criterion.rs +++ b/node-graph/graph-craft/benches/compile_demo_art_criterion.rs @@ -6,7 +6,9 @@ fn compile_to_proto(c: &mut Criterion) { for name in DEMO_ART { let network = load_from_name(name); - c.bench_function(name, |b: &mut criterion::Bencher<'_>| b.iter_batched(|| network.clone(), |mut network| black_box(network.flatten()), criterion::BatchSize::SmallInput)); + c.bench_function(name, |b: &mut criterion::Bencher<'_>| { + b.iter_batched(|| network.clone(), |mut network| black_box(network.compile()), criterion::BatchSize::SmallInput) + }); } } diff --git a/node-graph/graph-craft/benches/compile_demo_art_iai.rs b/node-graph/graph-craft/benches/compile_demo_art_iai.rs index 2e4b1e45da..a0548cab14 100644 --- a/node-graph/graph-craft/benches/compile_demo_art_iai.rs +++ b/node-graph/graph-craft/benches/compile_demo_art_iai.rs @@ -5,7 +5,7 @@ use iai_callgrind::{black_box, library_benchmark, library_benchmark_group, main} #[library_benchmark] #[benches::with_setup(args = ["isometric-fountain", "painted-dreams", "procedural-string-lights", "parametric-dunescape", "red-dress", "valley-of-spires"], setup = load_from_name)] pub fn compile_to_proto(mut input: NodeNetwork) { - let _ = black_box(input.flatten()); + let _ = black_box(input.compile()); } library_benchmark_group!(name = compile_group; benchmarks = compile_to_proto); diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 88431c281d..66393d83cd 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -40,6 +40,9 @@ pub struct DocumentNode { /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step. #[serde(default = "return_true")] pub visible: bool, + // Represents whether the output of the node should be cached. This is set to true whenever a node feeds into another node with more context dependencies + #[serde(default)] + pub cache_output: bool, pub manual_composition: Option, #[serde(default)] pub skip_deduplication: bool, @@ -50,6 +53,7 @@ impl Hash for DocumentNode { self.inputs.hash(state); self.implementation.hash(state); self.visible.hash(state); + self.cache_output.hash(state); } } @@ -59,6 +63,7 @@ impl Default for DocumentNode { inputs: Default::default(), implementation: Default::default(), visible: true, + cache_output: false, manual_composition: Some(generic!(T)), skip_deduplication: false, } @@ -518,7 +523,7 @@ impl NodeNetwork { /// Functions for compiling the network impl NodeNetwork { // Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation - pub fn flatten(&mut self) -> Result<(ProtoNetwork, Vec<(OriginalLocation, SNI)>), String> { + pub fn compile(&mut self) -> Result<(ProtoNetwork, Vec<(OriginalLocation, SNI)>), String> { // These three arrays are stored in parallel let mut protonetwork = Vec::new(); @@ -544,8 +549,8 @@ impl NodeNetwork { let Some(upstream_metadata) = upstream_metadata else { panic!("All inputs should be when the upstream SNI was generated"); }; - if upstream_metadata.is_value { - context_dependencies.add_dependencies(&upstream_metadata.context_dependencies); + if !upstream_metadata.is_value { + context_dependencies.add_dependencies(&upstream_metadata.nullify); } } // 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 @@ -554,9 +559,9 @@ impl NodeNetwork { panic!("All inputs should be when the upstream SNI was generated"); }; match upstream_metadata.is_value { - true => upstream_metadata.context_dependencies.difference(&context_dependencies), + false => upstream_metadata.nullify.difference(&context_dependencies), // If the upstream node is a Value node, do not nullify the context - false => upstream_metadata.context_dependencies = ContextDependencies::none(), + true => upstream_metadata.nullify = ContextDependencies::none(), } } (context_dependencies.clone(), false) @@ -575,10 +580,7 @@ impl NodeNetwork { }; (deduplicated_protonode.callers, deduplicated_protonode.original_location) } else { - ( - std::mem::take(&mut protonode.callers), - std::mem::replace(&mut protonode.original_location, OriginalLocation::Node(Vec::new())), - ) + (std::mem::take(&mut protonode.callers), protonode.original_location.clone()) }; // Map the callers inputs to the generated stable node id @@ -592,7 +594,7 @@ impl NodeNetwork { assert!(caller_index > current_protonode_index, "Caller index must be higher than current index"); nodes.inputs[input_index] = Some(UpstreamInputMetadata { input_sni: stable_node_id, - context_dependencies: protonode_context_dependencies.clone(), + nullify: protonode_context_dependencies.clone(), is_value: upstream_is_value, }) } @@ -726,7 +728,7 @@ impl NodeNetwork { log::error!("The node which was supposed to be flattened does not exist in the network, id {upstream_node_id}"); return; }; - + let cache_output = upstream_document_node.cache_output; match &upstream_document_node.implementation { DocumentNodeImplementation::Network(_node_network) => { let traversal_input = AbsoluteInputConnector { @@ -766,6 +768,7 @@ impl NodeNetwork { identifier, inputs: vec![None; number_of_inputs], context_dependencies, + cache_output, }); let protonode = ProtoNode { construction_args, diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index a414910769..9db5890352 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -13,6 +13,7 @@ use graphene_core::uuid::NodeId; use graphene_core::vector::style::Fill; use graphene_core::{Color, MemoHash, Node, Type}; use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata}; +use std::cell::Cell; use std::fmt::Display; use std::hash::Hash; use std::marker::PhantomData; @@ -396,6 +397,7 @@ impl Display for TaggedValue { pub struct UpcastNode { value: MemoHash, + inspected: Cell, } impl<'input> Node<'input, DAny<'input>> for UpcastNode { type Output = FutureAny<'input>; @@ -403,10 +405,15 @@ impl<'input> Node<'input, DAny<'input>> for UpcastNode { fn eval(&'input self, _: DAny<'input>) -> Self::Output { Box::pin(async move { self.value.clone().into_inner().to_dynany() }) } + + fn introspect(&self) -> graphene_core::memo::MonitorIntrospectResult { + let inspected = self.inspected.replace(true); + graphene_core::memo::MonitorIntrospectResult::Evaluated((Arc::new(self.value.clone().into_inner()) as Arc, !inspected)) + } } impl UpcastNode { pub fn new(value: MemoHash) -> Self { - Self { value } + Self { value, inspected: Cell::new(false) } } } #[derive(Default, Debug, Clone, Copy)] @@ -426,8 +433,6 @@ impl + Sync + Send, U: Sync + Send> UpcastAsRefNode { } } - - #[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] pub struct RenderOutput { pub data: RenderOutputType, @@ -540,25 +545,13 @@ mod fake_hash { macro_rules! thumbnail_render { ( $( $ty:ty ),* $(,)? ) => { - pub fn render_thumbnail_if_change(new_value: &Arc, old_value: Option<&Arc>) -> ThumbnailRenderResult { + pub fn render_thumbnail(new_value: &Arc) -> Option { $( 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 Some(new_value.render_thumbnail()); } )* - return ThumbnailRenderResult::ClearThumbnail; + return None; } }; } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index ce0f513675..f7a568f83e 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -96,7 +96,7 @@ impl ProtoNetwork { pub struct UpstreamInputMetadata { pub input_sni: SNI, // Context dependencies are accumulated during compilation, then replaced with the difference between the node's dependencies and the inputs dependencies - pub context_dependencies: ContextDependencies, + pub nullify: ContextDependencies, // If the upstream node is a value node, then do not nullify since the value nodes do not have a cache inserted after them pub is_value: bool, } @@ -112,6 +112,7 @@ pub struct NodeConstructionArgs { 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: ContextDependencies, + pub cache_output: bool, } #[derive(Debug, Clone)] @@ -258,7 +259,7 @@ impl Debug for GraphErrorType { } } -#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub struct GraphError { pub original_location: OriginalLocation, pub identifier: Cow<'static, str>, @@ -279,11 +280,7 @@ impl GraphError { } } } -impl Debug for GraphError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NodeGraphError").field("identifier", &self.identifier.to_string()).field("error", &self.error).finish() - } -} + pub type GraphErrors = Vec; /// The `TypingContext` is used to store the types of the nodes indexed by their stable node id. diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index 5179108602..77c1177943 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -185,7 +185,7 @@ fn compile_graph(document_string: String, application_io: Arc let mut wrapped_network = wrap_network_in_scope(network, Arc::new(FontCache::default()), EditorMetadata::default(), application_io); - wrapped_network.flatten().map(|result| result.0).map_err(|x| x.into()) + wrapped_network.compile().map(|result| result.0).map_err(|x| x.into()) } fn create_executor(proto_network: ProtoNetwork) -> Result> { diff --git a/node-graph/graster-nodes/src/std_nodes.rs b/node-graph/graster-nodes/src/std_nodes.rs index 9b331424d3..9da0082323 100644 --- a/node-graph/graster-nodes/src/std_nodes.rs +++ b/node-graph/graster-nodes/src/std_nodes.rs @@ -2,6 +2,7 @@ use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWar use dyn_any::DynAny; use fastnoise_lite; use glam::{DAffine2, DVec2, Vec2}; +use graphene_core::ExtractDownstreamTransform; use graphene_core::blending::AlphaBlending; use graphene_core::color::Color; use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut}; @@ -30,7 +31,7 @@ impl From for Error { } #[node_macro::node(category("Debug: Raster"))] -pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable) -> RasterDataTable { +pub fn sample_image(ctx: impl ExtractFootprint + ExtractDownstreamTransform + Clone + Send, image_frame: RasterDataTable) -> RasterDataTable { let mut result_table = RasterDataTable::default(); for mut image_frame_instance in image_frame.instance_iter() { @@ -40,8 +41,17 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Rast // Resize the image using the image crate let data = bytemuck::cast_vec(image.data.clone()); - let footprint = ctx.footprint(); - let viewport_bounds = footprint.viewport_bounds_in_local_space(); + // TODO: Feed as input for error handling + let Some(footprint) = ctx.try_footprint().cloned() else { + continue; + }; + + let Some(downstream_transform) = ctx.try_downstream_transform() else { + continue; + }; + + let viewport_bounds = footprint.viewport_bounds_in_local_space(downstream_transform); + let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox(); let intersection = viewport_bounds.intersect(&image_bounds); let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)); @@ -313,7 +323,7 @@ pub fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable) -> Ra #[node_macro::node(category("Raster: Pattern"))] #[allow(clippy::too_many_arguments)] pub fn noise_pattern( - ctx: impl ExtractFootprint + Ctx, + ctx: impl ExtractFootprint + ExtractDownstreamTransform + Ctx, _primary: (), clip: bool, seed: u32, @@ -331,8 +341,15 @@ pub fn noise_pattern( cellular_return_type: CellularReturnType, cellular_jitter: f64, ) -> RasterDataTable { - let footprint = ctx.footprint(); - let viewport_bounds = footprint.viewport_bounds_in_local_space(); + // TODO: Feed as input for error handling + let Some(footprint) = ctx.try_footprint().copied() else { + return RasterDataTable::default(); + }; + let Some(downstream_transform) = ctx.try_downstream_transform() else { + return RasterDataTable::default(); + }; + + let viewport_bounds = footprint.viewport_bounds_in_local_space(downstream_transform); let mut size = viewport_bounds.size(); let mut offset = viewport_bounds.start; @@ -468,9 +485,17 @@ pub fn noise_pattern( } #[node_macro::node(category("Raster: Pattern"))] -pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable { - let footprint = ctx.footprint(); - let viewport_bounds = footprint.viewport_bounds_in_local_space(); +pub fn mandelbrot(ctx: impl ExtractFootprint + ExtractDownstreamTransform + Send) -> RasterDataTable { + // TODO: Feed as input for error handling + let Some(footprint) = ctx.try_footprint().cloned() else { + return RasterDataTable::default(); + }; + + let Some(downstream_transform) = ctx.try_downstream_transform() else { + return RasterDataTable::default(); + }; + + let viewport_bounds = footprint.viewport_bounds_in_local_space(downstream_transform); let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox(); let intersection = viewport_bounds.intersect(&image_bounds); diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 6a95b33560..36890c0a26 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -1,15 +1,16 @@ 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::ContextDependencies; +use graphene_core::EditorContext; 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; +use graphene_core::uuid::SNI; pub use graphene_core::{Node, generic, ops}; +use std::sync::mpsc::Sender; pub trait IntoTypeErasedNode<'n> { fn into_type_erased(self) -> TypeErasedBox<'n>; @@ -72,63 +73,6 @@ pub fn downcast_node(n: SharedNodeContainer) -> Do // } // } -#[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_owned_context(&self) -> OwnedContextImpl { - 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); - } - context - // if let Some(editor_var_args) = self.editor_var_args { - // let (variable_names, values) - // context.set_varargs((variable_names, values)) - // } - } -} - pub struct NullificationNode { first: SharedNodeContainer, nullify: ContextDependencies, @@ -142,7 +86,9 @@ impl<'i> Node<'i, Any<'i>> for NullificationNode { Ok(context) => match *context { Some(context) => { let mut new_context: OwnedContextImpl = OwnedContextImpl::from(context); + // log::debug!("Nullifying context: {:?} fields: {:?}", new_context, self.nullify); new_context.nullify(&self.nullify); + // log::debug!("Evaluating input with: {:?}", new_context); Box::new(new_context.into_context()) as Any<'i> } None => { @@ -162,3 +108,40 @@ impl NullificationNode { Self { first, nullify } } } + +pub struct ContextMonitorNode { + sni: SNI, + input_index: usize, + first: SharedNodeContainer, + sender: Sender<(SNI, usize, EditorContext)>, +} + +impl<'i> Node<'i, Any<'i>> for ContextMonitorNode { + type Output = DynFuture<'i, Any<'i>>; + fn eval(&'i self, input: Any<'i>) -> Self::Output { + Box::pin(async move { + let new_input = match dyn_any::try_downcast::(input) { + Ok(context) => match *context { + Some(context) => { + let editor_context = context.to_editor_context(); + let _ = self.sender.clone().send((self.sni, self.input_index, editor_context)); + Box::new(Some(context)) as Any<'i> + } + None => { + let none: Context = None; + // self.sender.clone().send((self.sni, self.input_index, editor_context)); + Box::new(none) as Any<'i> + } + }, + Err(other_input) => other_input, + }; + self.first.eval(new_input).await + }) + } +} + +impl ContextMonitorNode { + pub fn new(sni: SNI, input_index: usize, first: SharedNodeContainer, sender: Sender<(SNI, usize, EditorContext)>) -> ContextMonitorNode { + ContextMonitorNode { sni, input_index, first, sender } + } +} diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 88892f5d1c..245ae342e4 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -212,11 +212,13 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity { 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 Some(bounds) = self.bounding_box(DAffine2::IDENTITY, true) else { + return String::new(); + }; let render_params = RenderParams { view_mode: ViewMode::Normal, - culling_bounds: bounds, + culling_bounds: Some(bounds), thumbnail: true, hide_artboards: false, for_export: false, @@ -224,15 +226,22 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity { alignment_parent_transform: None, }; - // Render the thumbnail data into an SVG string let mut render = SvgRender::new(); self.render_svg(&mut render, &render_params); + + // let center = (bounds[0] + bounds[1]) / 2.; + // let size = bounds[1] - bounds[0]; + + // let scale_x = 32.0 / size.x; + // let scale_y = 24.0 / size.y; + // let scale = scale_x.min(scale_y); + + // render.wrap_with_transform() // Give the SVG a viewbox and outer ... wrapper tag - // let [min, max] = bounds.unwrap_or_default(); - // render.format_svg(min, max); - + render.format_svg(bounds[0], bounds[1]); render.svg.to_svg_string() + // format!(r#"{}"#, scale, -center.x, -center.y, 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. diff --git a/node-graph/interpreted-executor/benches/benchmark_util.rs b/node-graph/interpreted-executor/benches/benchmark_util.rs index 51321b55a4..00e3f24265 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().0; + let proto_network = network.compile().unwrap().0; let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap(); (executor, proto_network) } diff --git a/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs b/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs index db7fcfb512..6497d257bd 100644 --- a/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs +++ b/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs @@ -7,7 +7,7 @@ use interpreted_executor::dynamic_executor::DynamicExecutor; fn update_executor(name: &str, c: &mut BenchmarkGroup) { let mut network = load_from_name(name); - let proto_network = network.flatten().unwrap().0; + let proto_network = network.compile().unwrap().0; let empty = ProtoNetwork::default(); let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); @@ -15,7 +15,7 @@ fn update_executor(name: &str, c: &mut BenchmarkGroup) { c.bench_function(name, |b| { b.iter_batched( || (executor.clone(), proto_network.clone()), - |(mut executor, network)| futures::executor::block_on(executor.update(black_box(network))), + |(mut executor, network)| futures::executor::block_on(executor.update(black_box(network, None))), criterion::BatchSize::SmallInput, ) }); @@ -30,7 +30,7 @@ fn update_executor_demo(c: &mut Criterion) { fn run_once(name: &str, c: &mut BenchmarkGroup) { let mut network = load_from_name(name); - let proto_network = network.flatten().unwrap().0; + let proto_network = network.compile().unwrap().0; let executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap(); let context = graphene_std::any::EditorContext::default(); diff --git a/node-graph/interpreted-executor/benches/update_executor.rs b/node-graph/interpreted-executor/benches/update_executor.rs index 102dcdd026..b7d3984fd6 100644 --- a/node-graph/interpreted-executor/benches/update_executor.rs +++ b/node-graph/interpreted-executor/benches/update_executor.rs @@ -16,7 +16,7 @@ fn update_executor(c: &mut Criterion) { let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); (executor, proto_network) }, - |(mut executor, network)| futures::executor::block_on(executor.update(criterion::black_box(network))), + |(mut executor, network)| futures::executor::block_on(executor.update(criterion::black_box(network, None))), criterion::BatchSize::SmallInput, ) }); diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index dc3b4a5413..84c6c0887c 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -4,12 +4,13 @@ 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::any::{EditorContext, NullificationNode}; +use graphene_std::any::{ContextMonitorNode, NullificationNode}; +use graphene_std::memo::{MonitorIntrospectResult, MonitorMemoNodeState}; use graphene_std::uuid::{NodeId, SNI}; -use graphene_std::{Context, ContextDependencies, NodeIOTypes}; +use graphene_std::{Context, ContextDependencies, EditorContext, NodeIOTypes}; use std::collections::{HashMap, HashSet}; use std::error::Error; -use std::sync::Arc; +use std::sync::mpsc::Sender; /// An executor of a node graph that does not require an online compilation server, and instead uses `Box`. #[derive(Clone)] @@ -21,7 +22,7 @@ pub struct DynamicExecutor { typing_context: TypingContext, // TODO: Add lifetime for removed nodes so that if a SNI changes, then changes back to its previous SNI, the node does // not have to be reinserted - // lifetime: HashSet<(SNI, usize)>, + // lifetime: HashSet<(Vec, usize)>, } impl Default for DynamicExecutor { @@ -45,13 +46,14 @@ impl DynamicExecutor { /// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible. #[cfg_attr(debug_assertions, inline(never))] - pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, NodeIOTypes)>, Vec), GraphErrors> { + pub async fn update(&mut self, proto_network: ProtoNetwork, context_sender: Option<&Sender<(SNI, usize, EditorContext)>>) -> Result<(Vec<(SNI, NodeIOTypes)>, Vec), GraphErrors> { self.output = Some(proto_network.output); self.typing_context.update(&proto_network)?; - let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?; + let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context, context_sender).await?; let mut remove = Vec::new(); for sni in orphaned_proto_nodes { remove.push(sni); + self.tree.free_node(&sni); self.typing_context.remove_inference(&sni); } @@ -70,9 +72,18 @@ impl DynamicExecutor { } // Introspect the cached output of any protonode - pub fn introspect(&self, protonode: SNI, check_if_evaluated: bool) -> Result>, IntrospectError> { + pub fn introspect(&self, protonode: SNI) -> Result { let inserted_node = self.tree.nodes.get(&protonode).ok_or(IntrospectError::ProtoNodeNotFound(protonode))?; - Ok(inserted_node.cached_protonode.introspect(check_if_evaluated)) + Ok(inserted_node.cached_protonode.introspect()) + } + + // If the cache is disabled, then it sets the state to save the first evaluation. If its enabled, then it does nothing + pub fn cache_first_evaluation(&self, protonode: &SNI) { + let Some(inserted_node) = self.tree.nodes.get(protonode) else { + log::error!("Could not get inserted protonode when setting cache_first_evaluation {:?}", protonode); + return; + }; + inserted_node.cached_protonode.cache_first_evaluation(); } pub fn input_type(&self) -> Option { @@ -188,7 +199,6 @@ struct InsertedProtonode { /// A store of the dynamically typed nodes and also the source map. #[derive(Default, Clone)] pub struct BorrowTree { - // A hashmap of node IDs to dynamically typed proto nodes, as well as the auto inserted MonitorCache nodes, and editor entry point nodes: HashMap, } @@ -196,13 +206,18 @@ impl BorrowTree { pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result { let mut nodes = BorrowTree::default(); for node in proto_network.into_nodes() { - nodes.push_node(node, typing_context).await? + nodes.push_node(node, typing_context, None).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: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec, HashSet), GraphErrors> { + pub async fn update( + &mut self, + proto_network: ProtoNetwork, + typing_context: &TypingContext, + context_sender: Option<&Sender<(SNI, usize, EditorContext)>>, + ) -> 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(); @@ -211,7 +226,7 @@ impl BorrowTree { old_nodes.remove(&sni); if !self.nodes.contains_key(&sni) { nodes_with_new_type.push(sni); - self.push_node(node, typing_context).await?; + self.push_node(node, typing_context, context_sender.clone()).await?; } } @@ -333,7 +348,7 @@ impl BorrowTree { /// - 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> { + async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext, context_sender: Option<&Sender<(SNI, usize, EditorContext)>>) -> Result<(), GraphErrors> { let sni = proto_node.stable_node_id; match proto_node.construction_args { ConstructionArgs::Value(value) => { @@ -371,10 +386,23 @@ impl BorrowTree { 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; + .enumerate() + .map(|(input_index, (upstream_inserted_protonode, input_metadata))| { + let mut previous_input = upstream_inserted_protonode.cached_protonode.clone(); + + // Insert context monitoring if enabled + if let Some(context_sender) = context_sender { + let context_monitor = ContextMonitorNode::new(sni, input_index, previous_input, context_sender.clone()); + let node = Box::new(context_monitor) as TypeErasedBox<'_>; + previous_input = NodeContainer::new(node); + } + + let input_context_dependencies = input_metadata.unwrap().nullify; if !input_context_dependencies.is_empty() { + // If nullifying the inputs such that the context is completely empty, then cache the upstream output + if upstream_inserted_protonode.nullify_when_calling == ContextDependencies::all_context_dependencies() { + upstream_inserted_protonode.cached_protonode.permanently_enable_cache(); + } let nullification_node = NullificationNode::new(previous_input, input_context_dependencies); let node = Box::new(nullification_node) as TypeErasedBox<'_>; NodeContainer::new(node) @@ -387,18 +415,20 @@ impl BorrowTree { let node = constructor(protonode_inputs).await; let protonode = NodeContainer::new(node); - // Insert cache nodes on the output if possible + // When evaluating the node from the editor, nullify all context fields it is not dependent on + let nullify_when_calling = node_construction_args.context_dependencies.inverse(); + let cached_protonode = if let Some(cache_constructor) = typing_context.cache_constructor(&types.return_value.nested_type()) { - let cache = cache_constructor(protonode); + let cache = cache_constructor(protonode, MonitorMemoNodeState::Disabled); let cache_node_container = NodeContainer::new(cache); + if node_construction_args.cache_output { + cache_node_container.permanently_enable_cache(); + } cache_node_container } else { protonode }; - // When evaluating the node from the editor, nullify all context fields it is not dependent on - let nullify_when_calling = node_construction_args.context_dependencies.inverse(); - let inserted_protonode = InsertedProtonode { cached_protonode, nullify_when_calling, @@ -411,27 +441,27 @@ impl BorrowTree { } } -#[cfg(test)] -mod test { - use super::*; - use graph_craft::{document::value::TaggedValue, proto::NodeValueArgs}; - use graphene_std::uuid::NodeId; +// #[cfg(test)] +// mod test { +// use super::*; +// use graph_craft::{document::value::TaggedValue, proto::NodeValueArgs}; +// use graphene_std::uuid::NodeId; - #[test] - fn push_node_sync() { - let mut tree = BorrowTree::default(); - let val_1_protonode = ProtoNode::value( - ConstructionArgs::Value(NodeValueArgs { - value: Some(TaggedValue::U32(2u32).into()), - connector_paths: Vec::new(), - }), - NodeId(0), - ); - let context = TypingContext::default(); - let future = tree.push_node(val_1_protonode, &context); - futures::executor::block_on(future).unwrap(); - let _node = tree.nodes.get(&NodeId(0)).expect("Node should be added to tree"); - let result = futures::executor::block_on(tree.eval_tagged_value(NodeId(0), ())); - assert_eq!(result, Some(TaggedValue::U32(2u32).into())); - } -} +// #[test] +// fn push_node_sync() { +// let mut tree = BorrowTree::default(); +// let val_1_protonode = ProtoNode::value( +// ConstructionArgs::Value(NodeValueArgs { +// value: Some(TaggedValue::U32(2u32).into()), +// connector_paths: Vec::new(), +// }), +// NodeId(0), +// ); +// let context = TypingContext::default(); +// let future = tree.push_node(val_1_protonode, &context); +// futures::executor::block_on(future).unwrap(); +// let _node = tree.nodes.get(&NodeId(0)).expect("Node should be added to tree"); +// let result = futures::executor::block_on(tree.eval_tagged_value(NodeId(0), ())); +// assert_eq!(result, Some(TaggedValue::U32(2u32).into())); +// } +// } diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index c58ba6854b..10fbbb419c 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -42,7 +42,7 @@ mod tests { use crate::dynamic_executor::DynamicExecutor; - let protonetwork = network.flatten().map(|result| result.0).expect("Graph should be generated"); + let protonetwork = network.compile().map(|result| result.0).expect("Graph should be generated"); let _exec = block_on(DynamicExecutor::new(protonetwork)).map(|_e| panic!("The network should not type check ")).unwrap_err(); } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 3f17b3230b..76ccaf72d8 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -332,8 +332,8 @@ mod node_registry_macros { macro_rules! cache_node { ($type:ty) => { - (concrete!($type), |arg| { - let node = >::new(graphene_std::registry::downcast_node::(arg)); + (concrete!($type), |arg, state| { + let node = >::new(graphene_std::registry::downcast_node::(arg), state); let any: DynAnyNode<_, _, _> = graphene_std::any::DynAnyNode::new(node); Box::new(any) as TypeErasedBox }) diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 3a3f53ca48..037fa2e849 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -643,6 +643,14 @@ impl ParsedNodeFn { "ExtractAnimationTime" => dependency_tokens.push(quote::quote! {ExtractAnimationTime}), "ExtractIndex" => dependency_tokens.push(quote::quote! {ExtractIndex}), "ExtractVarArgs" => dependency_tokens.push(quote::quote! {ExtractVarArgs}), + "ExtractAll" => { + dependency_tokens.push(quote::quote! {ExtractFootprint}); + dependency_tokens.push(quote::quote! {ExtractDownstreamTransform}); + dependency_tokens.push(quote::quote! {ExtractRealTime}); + dependency_tokens.push(quote::quote! {ExtractAnimationTime}); + dependency_tokens.push(quote::quote! {ExtractIndex}); + dependency_tokens.push(quote::quote! {ExtractVarArgs}); + } _ => {} } }