merge onto master

This commit is contained in:
Adam
2025-07-06 14:04:44 -07:00
parent 99966d848d
commit 1398405529
60 changed files with 2861 additions and 3229 deletions

View File

@@ -5,7 +5,8 @@ use crate::messages::prelude::*;
#[derive(Debug, Default)]
pub struct Dispatcher {
buffered_queue: Option<Vec<VecDeque<Message>>>,
buffered_queue: Vec<Message>,
queueing_messages: bool,
message_queues: Vec<VecDeque<Message>>,
pub responses: Vec<FrontendMessage>,
pub message_handlers: DispatcherMessageHandlers,
@@ -90,11 +91,10 @@ impl Dispatcher {
pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
let message = message.into();
// Add all additional messages to the buffer if it exists (except from the end buffer message)
if !matches!(message, Message::EndBuffer { .. }) {
if let Some(buffered_queue) = &mut self.buffered_queue {
Self::schedule_execution(buffered_queue, true, [message]);
// Add all additional messages to the queue if it exists (except from the end queue message)
if !matches!(message, Message::EndQueue) {
if self.queueing_messages {
self.buffered_queue.push(message);
return;
}
}
@@ -126,6 +126,41 @@ impl Dispatcher {
// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
match message {
Message::StartQueue => {
self.queueing_messages = true;
}
Message::EndQueue => {
self.queueing_messages = false;
}
Message::ProcessQueue((render_output_metadata, introspected_inputs)) => {
let message = PortfolioMessage::ProcessEvaluationResponse {
evaluation_metadata: render_output_metadata,
introspected_inputs,
};
// Add the message to update the state with the render output
Self::schedule_execution(&mut self.message_queues, true, [message]);
// Schedule all queued messages to be run (in the order they were added)
Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.buffered_queue));
}
Message::NoOp => {}
Message::Init => {
// Load persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
queue.add(FrontendMessage::TriggerLoadPreferences);
// Display the menu bar at the top of the window
queue.add(MenuBarMessage::SendLayout);
// Send the information for tooltips and categories for each node/input.
queue.add(FrontendMessage::SendUIMetadata {
node_descriptions: document_node_definitions::collect_node_descriptions(),
node_types: document_node_definitions::collect_node_types(),
});
// Finish loading persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
}
Message::Animation(message) => {
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
}

View File

@@ -84,7 +84,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
}
AnimationMessage::SetFrameIndex { frame } => {
self.frame_index = frame;
responses.add(PortfolioMessage::SubmitActiveGraphRender);
responses.add(PortfolioMessage::EvaluateActiveDocument);
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
@@ -100,7 +100,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
}
AnimationMessage::UpdateTime => {
if self.is_playing() {
responses.add(PortfolioMessage::SubmitActiveGraphRender);
responses.add(PortfolioMessage::EvaluateActiveDocument);
if self.live_preview_recently_zero {
// Update the restart and pause/play buttons
@@ -116,7 +116,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
_ => AnimationState::Stopped,
};
self.live_preview_recently_zero = true;
responses.add(PortfolioMessage::SubmitActiveGraphRender);
responses.add(PortfolioMessage::EvaluateActiveDocument);
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}

View File

@@ -43,7 +43,7 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background,
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area,
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport {
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::ActiveDocumentExport {
file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
file_type: self.file_type,
scale_factor: self.scale_factor,

View File

@@ -24,7 +24,7 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
if create_artboard {
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
responses.add(GraphOperationMessage::NewArtboard {
id: NodeId::new(),
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
@@ -33,7 +33,7 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
responses.add(DocumentMessage::DeselectAllLayers);
}

View File

@@ -174,7 +174,7 @@ impl PreferencesDialogMessageHandler {
let use_vello = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu())
CheckboxInput::new(preferences.use_vello())
.tooltip(vello_tooltip)
.disabled(!preferences.supports_wgpu())
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())

View File

@@ -14,6 +14,9 @@ use graphene_std::text::Font;
#[impl_message(Message, Frontend)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendMessage {
ClearNodeThumbnail {
sni: NodeId,
},
// Display prefix: make the frontend show something, like a dialog
DisplayDialog {
title: String,
@@ -272,10 +275,6 @@ pub enum FrontendMessage {
UpdateNodeGraphTransform {
transform: Transform,
},
UpdateNodeThumbnail {
id: NodeId,
value: String,
},
UpdateOpenDocumentsList {
#[serde(rename = "openDocuments")]
open_documents: Vec<FrontendDocumentDetails>,
@@ -285,6 +284,11 @@ pub enum FrontendMessage {
layout_target: LayoutTarget,
diff: Vec<WidgetDiff>,
},
UpdateThumbnails {
add: Vec<(NodeId, String)>,
clear: Vec<NodeId>,
// remove: Vec<NodeId>,
},
UpdateToolOptionsLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,

View File

@@ -14,7 +14,7 @@ pub struct InputPreprocessorMessageContext {
#[derive(Debug, Default, ExtractField)]
pub struct InputPreprocessorMessageHandler {
pub frame_time: FrameTimeInfo,
pub time: u64,
pub time: f64,
pub keyboard: KeyStates,
pub mouse: MouseState,
pub viewport_bounds: ViewportBounds,
@@ -98,9 +98,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
self.translate_mouse_event(mouse_state, false, responses);
}
InputPreprocessorMessage::CurrentTime { timestamp } => {
responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
self.time = timestamp;
self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
self.time = timestamp as f64;
}
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
@@ -187,10 +185,19 @@ impl InputPreprocessorMessageHandler {
}
}
pub fn document_bounds(&self) -> [DVec2; 2] {
pub fn viewport_bounds(&self) -> [DVec2; 2] {
// IPP bounds are relative to the entire application
[(0., 0.).into(), self.viewport_bounds.bottom_right - self.viewport_bounds.top_left]
}
pub fn document_bounds(&self, document_to_viewport: DAffine2) -> [DVec2; 2] {
// IPP bounds are relative to the entire application
let mut bounds = self.viewport_bounds();
for point in &mut bounds {
*point = document_to_viewport.transform_point2(*point);
}
bounds
}
}
#[cfg(test)]

View File

@@ -1,11 +1,27 @@
use std::sync::Arc;
use crate::messages::prelude::*;
use graphene_std::renderer::RenderMetadata;
use graphene_std::{IntrospectMode, uuid::CompiledProtonodeInput};
use graphite_proc_macros::*;
#[impl_message]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq)]
pub enum Message {
// Sub-messages
NoOp,
Init,
Batched(Box<[Message]>),
// Adds any subsequent messages to the queue
StartQueue,
// Stop adding messages to the queue.
EndQueue,
// Processes all messages that are queued, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete
ProcessQueue(
(
graphene_std::renderer::RenderMetadata,
Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
),
),
#[child]
Animation(AnimationMessage),
#[child]

View File

@@ -7,12 +7,13 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
use crate::messages::portfolio::utility_types::PanelType;
use crate::messages::prelude::*;
use glam::DAffine2;
use graph_craft::document::NodeId;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::Color;
use graphene_std::raster::BlendMode;
use graphene_std::raster::Image;
use graphene_std::renderer::ClickTarget;
use graphene_std::transform::Footprint;
use graphene_std::vector::click_target::ClickTarget;
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::ViewMode;
#[impl_message(Message, PortfolioMessage, Document)]

View File

@@ -33,9 +33,11 @@ use graphene_std::math::quad::Quad;
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{Raster, RasterDataTable};
use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode;
use std::sync::Arc;
use std::time::Duration;
#[derive(ExtractField)]
@@ -43,10 +45,11 @@ pub struct DocumentMessageContext<'a> {
pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub executor: &'a mut NodeGraphExecutor,
pub current_tool: &'a ToolType,
pub preferences: &'a PreferencesMessageHandler,
pub device_pixel_ratio: f64,
// pub introspected_inputs: &HashMap<CompiledProtonodeInput, Box<dyn std::any::Any + Send + Sync>>,
// pub downcasted_inputs: &mut HashMap<CompiledProtonodeInput, TaggedValue>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
@@ -104,10 +107,10 @@ pub struct DocumentMessageHandler {
//
/// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks
#[serde(skip)]
breadcrumb_network_path: Vec<NodeId>,
pub breadcrumb_network_path: Vec<NodeId>,
/// Path to network that is currently selected. Updated based on the most recently clicked panel.
#[serde(skip)]
selection_network_path: Vec<NodeId>,
pub selection_network_path: Vec<NodeId>,
/// Stack of document network snapshots for previous history states.
#[serde(skip)]
document_undo_history: VecDeque<NodeNetworkInterface>,
@@ -176,11 +179,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
document_id,
ipp,
persistent_data,
executor,
current_tool,
preferences,
device_pixel_ratio,
} = context;
// introspected_inputs,
// downcasted_inputs
} = data;
let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path);
let selected_visible_layers_bounding_box_viewport = self.selected_visible_layers_bounding_box_viewport();
@@ -342,7 +346,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
node_ids: vec![node_id],
delete_children: true,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::EndTransaction);
@@ -441,7 +445,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
}
let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
DocumentMessage::EnterNestedNetwork { node_id } => {
self.breadcrumb_network_path.push(node_id);
@@ -713,7 +717,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
}
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph);
}
DocumentMessage::MoveSelectedLayersToGroup { parent } => {
@@ -729,7 +733,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
}
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![parent.to_node()] });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph);
}
@@ -1152,7 +1156,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
DocumentMessage::SetNodePinned { node_id, pinned } => {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::SetPinned { node_id, pinned });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
}
@@ -1215,7 +1219,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
}
DocumentMessage::SetViewMode { view_mode } => {
self.view_mode = view_mode;
responses.add_front(NodeGraphMessage::RunDocumentGraph);
responses.add_front(PortfolioMessage::CompileActiveDocument);
}
DocumentMessage::AddTransaction => {
// Reverse order since they are added to the front
@@ -1306,6 +1310,17 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
// DocumentMessage::ToggleAnimation => match self.animation_state {
// AnimationState::Stopped => {self.animation_state = AnimationState::Playing { start: ipp.time }; responses.add(PortfolioMessage::EvaluateActiveDocument)},
// AnimationState::Playing { start } => self.animation_state = AnimationState::Paused { start , pause_time: ipp.time },
// AnimationState::Paused { start, .. } => {self.animation_state = AnimationState::Playing { start }; responses.add(PortfolioMessage::EvaluateActiveDocument)},
// },
// DocumentMessage::RestartAnimation => {
// self.animation_state = match self.animation_state {
// AnimationState::Playing { .. } => AnimationState::Playing { start: ipp.time },
// _ => AnimationState::Stopped,
// };
// }
DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints,
local_transforms,
@@ -1364,7 +1379,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(DocumentMessage::UngroupLayer { layer: folder });
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph);
}
@@ -1402,7 +1417,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
node_ids: vec![layer.to_node()],
delete_children: true,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
}
@@ -1417,7 +1432,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
center: Key::Alt,
duplicate: Key::Alt,
}));
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
} else {
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
return;
@@ -1901,11 +1916,11 @@ impl DocumentMessageHandler {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(Message::StartBuffer);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartQueue);
Some(previous_network)
}
pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
@@ -1934,7 +1949,7 @@ impl DocumentMessageHandler {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
Some(previous_network)
@@ -2062,7 +2077,7 @@ impl DocumentMessageHandler {
if let (Some(upstream_boolean_op), Some(only_selected_layer)) = (upstream_boolean_op, only_selected_layer) {
network_interface.set_input(&InputConnector::node(upstream_boolean_op, 1), NodeInput::value(TaggedValue::BooleanOperation(operation), false), &[]);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
return only_selected_layer.to_node();
}
@@ -2874,7 +2889,7 @@ impl DocumentMessageHandler {
}
if modified {
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph);
}
}

View File

@@ -126,7 +126,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
}
}
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
GraphOperationMessage::NewBitmapLayer {
id,
@@ -138,7 +138,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_image_data(image_frame, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
GraphOperationMessage::NewBooleanOperationLayer { id, operation, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
@@ -149,7 +149,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
node_id: id,
alias: "Boolean Operation".to_string(),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
@@ -169,14 +169,14 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
}
// Move the layer and all nodes to the correct position in the network
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_vector_data(subpaths, layer, true, true, true);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
GraphOperationMessage::NewTextLayer {
id,
@@ -191,7 +191,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
modify_inputs.insert_text(text, font, typesetting, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke: Stroke::default() });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
GraphOperationMessage::ResizeArtboard { layer, location, dimensions } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
@@ -279,7 +279,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
});
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
}

View File

@@ -454,7 +454,7 @@ impl<'a> ModifyInputsContext<'a> {
// Refresh the render and editor UI
self.responses.add(PropertiesPanelMessage::Refresh);
if !skip_rerender {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
self.responses.add(PortfolioMessage::CompileActiveDocument);
}
}
@@ -462,7 +462,7 @@ impl<'a> ModifyInputsContext<'a> {
let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
self.network_interface.vector_modify(&path_node_id, modification_type);
self.responses.add(PropertiesPanelMessage::Refresh);
self.responses.add(NodeGraphMessage::RunDocumentGraph);
self.responses.add(PortfolioMessage::CompileActiveDocument);
}
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
@@ -495,7 +495,7 @@ impl<'a> ModifyInputsContext<'a> {
self.network_interface.set_input(&input_connector, input, &[]);
self.responses.add(PropertiesPanelMessage::Refresh);
if !skip_rerender {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
self.responses.add(PortfolioMessage::CompileActiveDocument);
}
}
}

View File

@@ -30,7 +30,6 @@ use std::collections::{HashMap, HashSet, VecDeque};
pub struct NodePropertiesContext<'a> {
pub persistent_data: &'a PersistentData,
pub responses: &'a mut VecDeque<Message>,
pub executor: &'a mut NodeGraphExecutor,
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub document_name: &'a str,

View File

@@ -208,12 +208,6 @@ pub enum NodeGraphMessage {
UpdateImportsExports,
UpdateLayerPanel,
UpdateNewNodeGraph,
UpdateTypes {
#[serde(skip)]
resolved_types: ResolvedDocumentNodeTypesDelta,
#[serde(skip)]
node_graph_errors: GraphErrors,
},
UpdateActionButtons,
UpdateGraphBarRight,
UpdateInSelectedNetwork,

View File

@@ -180,7 +180,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer });
responses.add(PropertiesPanelMessage::Refresh);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
NodeGraphMessage::CreateNodeFromContextMenu {
node_id,
@@ -241,7 +241,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
input_connector: InputConnector::node(node_id, input_index),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
self.wire_in_progress_from_connector = None;
@@ -283,7 +283,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),
delete_children,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
}
@@ -560,7 +560,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
});
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![encapsulating_node_id] });
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index } => {
network_interface.move_layer_to_stack(layer, parent, insert_index, selection_network_path);
@@ -890,7 +890,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::DisconnectInput { input_connector: *disconnecting });
}
// Update the frontend that the node is disconnected
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph);
self.disconnecting = None;
}
@@ -1064,7 +1064,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
output_connector: *output_connector,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph);
} else if output_connector.is_some() && input_connector.is_none() && !self.initial_disconnecting {
@@ -1222,7 +1222,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
input_connector: *overlapping_wire,
insert_node_input_index: selected_node_input_connect_index,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph);
}
}
@@ -1273,24 +1273,24 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
NodeGraphMessage::RemoveImport { import_index: usize } => {
network_interface.remove_import(usize, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
NodeGraphMessage::RemoveExport { export_index: usize } => {
network_interface.remove_export(usize, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
NodeGraphMessage::ReorderImport { start_index, end_index } => {
network_interface.reorder_import(start_index, end_index, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
NodeGraphMessage::ReorderExport { start_index, end_index } => {
network_interface.reorder_export(start_index, end_index, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
NodeGraphMessage::RunDocumentGraph => {
PortfolioMessage::CompileActiveDocument => {
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
}
NodeGraphMessage::ForceRunDocumentGraph => {
@@ -1340,10 +1340,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
return;
};
let viewport_bbox = ipp.document_bounds();
let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));
let document_bbox: [DVec2; 2] = ipp.document_bounds();
let mut nodes = Vec::new();
for node_id in &self.frontend_nodes {
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
@@ -1395,7 +1392,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
});
responses.add(PropertiesPanelMessage::Refresh);
if !(network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) {
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
}
NodeGraphMessage::SetInput { input_connector, input } => {
@@ -1468,7 +1465,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
});
}
if selected_nodes.selected_nodes().any(|node_id| network_interface.connected_to_output(node_id, selection_network_path)) {
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
}
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
@@ -1486,7 +1483,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(FrontendMessage::UpdateContextMenuInformation {
context_menu_information: self.context_menu.clone(),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::SendWires);
}
@@ -1521,7 +1518,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::TogglePreviewImpl { node_id });
responses.add(NodeGraphMessage::UpdateActionButtons);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
NodeGraphMessage::TogglePreviewImpl { node_id } => {
network_interface.toggle_preview(node_id, selection_network_path);
@@ -1606,7 +1603,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
}
NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids } => {
if node_ids.iter().any(|node_id| network_interface.connected_to_output(node_id, selection_network_path)) {
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
responses.add(NodeGraphMessage::UpdateActionButtons);
responses.add(NodeGraphMessage::SendGraph);
@@ -1717,15 +1714,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => {
for (path, node_type) in resolved_types.add {
network_interface.resolved_types.types.insert(path.to_vec(), node_type);
}
for path in resolved_types.remove {
network_interface.resolved_types.types.remove(&path.to_vec());
}
self.node_graph_errors = node_graph_errors;
}
NodeGraphMessage::UpdateActionButtons => {
if selection_network_path == breadcrumb_network_path {
self.update_graph_bar_left(network_interface, breadcrumb_network_path, responses);

View File

@@ -5,15 +5,21 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
use crate::node_graph_executor::NodeGraphExecutor;
#[derive(ExtractField)]
pub struct PropertiesPanelMessageContext<'a> {
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use graph_craft::document::NodeId;
pub struct PropertiesPanelMessageHandlerData<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub document_name: &'a str,
}
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use graph_craft::document::NodeId;
pub struct PropertiesPanelMessageHandlerData<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub document_name: &'a str,
pub executor: &'a mut NodeGraphExecutor,
pub persistent_data: &'a PersistentData,
}
#[derive(Debug, Clone, Default, ExtractField)]
@@ -26,9 +32,7 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
network_interface,
selection_network_path,
document_name,
executor,
persistent_data,
} = context;
} = data;
match message {
PropertiesPanelMessage::Clear => {
@@ -44,7 +48,6 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
network_interface,
selection_network_path,
document_name,
executor,
};
let properties_sections = NodeGraphMessageHandler::collate_properties(&mut node_properties_context);

View File

@@ -11,12 +11,14 @@ use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, InputConnector, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork, OutputConnector};
use graph_craft::{Type, concrete};
use graphene_std::math::quad::Quad;
use graphene_std::transform::Footprint;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId};
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData, VectorModificationType};
use graphene_std::{CompiledProtonodeInput, SNI};
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
use interpreted_executor::node_registry::NODE_REGISTRY;
use serde_json::{Value, json};
@@ -40,6 +42,8 @@ pub struct NodeNetworkInterface {
pub resolved_types: ResolvedDocumentNodeTypes,
#[serde(skip)]
transaction_status: TransactionStatus,
#[serde(skip)]
current_hash: u64,
}
impl Clone for NodeNetworkInterface {
@@ -478,6 +482,13 @@ impl NodeNetworkInterface {
node_template
}
pub fn hash_changed(&mut self) -> bool {
let old_hash = self.current_hash;
let new_hash = self.network.current_hash();
self.current_hash = new_hash;
old_hash != new_hash
}
/// Try and get the [`DocumentNodeDefinition`] for a node
pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> {
let metadata = self.node_metadata(&node_id, network_path)?;
@@ -501,66 +512,86 @@ impl NodeNetworkInterface {
}
}
/// Try and get the [`Type`] for any [`InputConnector`] based on the `self.resolved_types`.
fn node_type_from_compiled(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<(Type, TypeSource)> {
let (node_id, input_index) = match *input_connector {
InputConnector::Node { node_id, input_index } => (node_id, input_index),
InputConnector::Export(export_index) => {
let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else {
// The outermost network export defaults to an ArtboardGroupTable.
return Some((concrete!(graphene_std::ArtboardGroupTable), TypeSource::OuterMostExportDefault));
};
let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path);
return Some(output_type);
pub fn downstream_caller_from_output(&self, output_connector: OutputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> {
match output_connector {
OutputConnector::Node { node_id, output_index } => match self.implementation(&node_id, network_path)? {
DocumentNodeImplementation::Network(node_network) => {
let mut nested_path = network_path.to_vec();
nested_path.push(node_id);
self.downstream_caller_from_input(InputConnector::Export(output_index), &nested_path)
}
DocumentNodeImplementation::ProtoNode(_) => self.node_metadata(&node_id, network_path)?.transient_metadata.caller.as_ref(),
DocumentNodeImplementation::Extract => todo!(),
},
OutputConnector::Import(import_index) => {
let mut encapsulating_path = network_path.to_vec();
let node_id = encapsulating_path.pop().expect("No imports in document network");
self.downstream_caller_from_input(InputConnector::node(node_id, import_index), &encapsulating_path)
}
}
}
// Returns the path and input index to the protonode which called the input, which has to be the same every time is is called for a given input.
// This has to be done by iterating upstream, since a downstream traversal may lead to an uncompiled branch.
// This requires that value inputs store their caller. Caller input metadata from compilation has to be stored for
pub fn downstream_caller_from_input(&self, &input_connector: InputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> {
// Cases: Node/Value input to protonode, Node/Value input to network node
let input = self.input_from_connector(input_connector, network_path)?;
let caller_input = match input {
NodeInput::Node { node_id, output_index, lambda } => {
match self.implementation(node_id, network_path)? {
DocumentNodeImplementation::Network(node_network) => {
// Continue traversal within network
let mut nested_path = network_path.to_vec();
nested_path.push(*node_id);
self.downstream_caller_from_input(InputConnector::Export(*output_index), &nested_path)
}
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => self.node_metadata(node_id, network_path)?.transient_metadata.caller.as_ref(),
// If connected to a protonode, use the data in the node metadata
DocumentNodeImplementation::Extract => todo!(),
}
}
// Can either be an input to a protonode, network node, or export
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Reflection(_) => match input_connector {
InputConnector::Node { node_id, input_index } => self.input_metadata(node_id, *index, network_path)?.transient_metadata.caller.as_ref(),
InputConnector::Export(export_index) => self.network_metadata(network_path)?.transient_metadata.callers.get(export_index)?.as_ref(),
},
NodeInput::Network { import_index } => {
let mut encapsulating_path = network_path.to_vec();
let node_id = encapsulating_path.pop().expect("No imports in document network");
self.downstream_caller_from_input(InputConnector::node(node_id, *import_index), &encapsulating_path)
}
NodeInput::Inline(inline_rust) => None,
};
let Some(node) = self.document_node(&node_id, network_path) else {
log::error!("Could not get node {node_id} in input_type");
let Some(caller_input) = caller_input else {
log::error!("Could not get compiled caller input for input: {:?}", input_connector);
return None;
};
// If the input_connector is a NodeInput::Value, return the type of the tagged value.
if let Some(value) = node.inputs.get(input_index).and_then(|input| input.as_value()) {
return Some((value.ty(), TypeSource::TaggedValue));
}
let node_id_path = [network_path, &[node_id]].concat();
match &node.implementation {
DocumentNodeImplementation::Network(_nested_network) => {
// Attempt to resolve where this import is within the nested network (it may be connected to the node or directly to an export)
let outwards_wires = self.outward_wires(&node_id_path);
let inputs_using_import = outwards_wires.and_then(|outwards_wires| outwards_wires.get(&OutputConnector::Import(input_index)));
let first_input = inputs_using_import.and_then(|input| input.first()).copied();
Some(caller_input)
}
if inputs_using_import.is_some_and(|inputs| inputs.len() > 1) {
warn!("Found multiple inputs using an import. Using the type of the first one.");
}
if let Some(input_connector) = first_input {
self.node_type_from_compiled(&input_connector, &node_id_path)
}
// Nothing is connected to the import
else {
None
}
pub fn take_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<NodeInput> {
let Some(network) = self.network_mut(network_path) else {
log::error!("Could not get network in input_from_connector");
return None;
};
let input = match input_connector {
InputConnector::Node { node_id, input_index } => {
let Some(node) = network.nodes.get_mut(node_id) else {
log::error!("Could not get node {node_id} in input_from_connector");
return None;
};
node.inputs.get_mut(*input_index)
}
DocumentNodeImplementation::ProtoNode(_) => {
// If a node has manual composition, then offset the input index by 1 since the proto node also includes the type of the input passed through manual composition.
let manual_composition_offset = if node.manual_composition.is_some() { 1 } else { 0 };
self.resolved_types
.types
.get(node_id_path.as_slice())
.and_then(|node_types| node_types.inputs.get(input_index + manual_composition_offset).cloned())
.map(|node_types| (node_types, TypeSource::Compiled))
}
DocumentNodeImplementation::Extract => None,
}
InputConnector::Export(export_index) => network.exports.get_mut(*export_index),
};
input.map(|input| std::mem::replace(input, NodeInput::value(TaggedValue::None, true)))
}
/// Guess the type from the node based on a document node default or a random protonode definition.
fn guess_type_from_node(&mut self, network_path: &mut Vec<NodeId>, node_id: NodeId, input_index: usize) -> (Type, TypeSource) {
fn guess_type_from_node(&mut self, node_id: NodeId, input_index: usize, network_path: &[NodeId]) -> (Type, TypeSource) {
// Try and get the default value from the document node definition
if let Some(value) = self
.get_node_definition(network_path, node_id)
.node_definition(node_id, network_path)
.and_then(|definition| definition.node_template.document_node.inputs.get(input_index))
.and_then(|input| input.as_value())
{
@@ -571,21 +602,21 @@ impl NodeNetworkInterface {
return (concrete!(()), TypeSource::Error("node id {node_id:?} not in network {network_path:?}"));
};
let node_id_path = [network_path.as_slice(), &[node_id]].concat();
let mut node_id_path = network_path.to_vec();
node_id_path.push(node_id);
match &node.implementation {
DocumentNodeImplementation::ProtoNode(protonode) => {
let Some(node_types) = random_protonode_implementation(protonode) else {
return (concrete!(()), TypeSource::Error("could not resolve protonode"));
};
let skip_footprint = if node.manual_composition.is_some() { 1 } else { 0 };
let Some(input_type) = std::iter::once(node_types.call_argument.clone()).chain(node_types.inputs.clone()).nth(input_index + skip_footprint) else {
let Some(input_type) = node_types.inputs.get(input_index) else {
log::error!("Could not get type");
return (concrete!(()), TypeSource::Error("could not get the protonode's input"));
};
(input_type, TypeSource::RandomProtonodeImplementation)
(input_type.clone(), TypeSource::RandomProtonodeImplementation)
}
DocumentNodeImplementation::Network(_network) => {
// Attempt to resolve where this import is within the nested network
@@ -598,9 +629,10 @@ impl NodeNetworkInterface {
input_index: child_input_index,
}) = first_input
{
network_path.push(node_id);
let result = self.guess_type_from_node(network_path, child_id, child_input_index);
network_path.pop();
let mut inner_path = network_path.to_vec();
inner_path.push(node_id);
let result = self.guess_type_from_node(child_id, child_input_index, inner_path);
inner_path.pop();
return result;
}
@@ -613,8 +645,11 @@ impl NodeNetworkInterface {
/// Get the [`Type`] for any InputConnector
pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) {
if let Some(result) = self.node_type_from_compiled(input_connector, network_path) {
return result;
if let Some(compiled_type) = self
.downstream_caller_from_input(input_connector, network_path)
.and_then(|(sni, input_index)| self.resolved_types.get(sni).and_then(|protonode_input_types| protonode_input_types.get(*input_index)))
{
return (compiled_type.clone(), TypeSource::Compiled);
}
// Resolve types from proto nodes in node_registry
@@ -622,9 +657,57 @@ impl NodeNetworkInterface {
return (concrete!(()), TypeSource::Error("input connector is not a node"));
};
// TODO: Once there is type inference (#1621), replace this workaround approach when disconnecting node inputs with NodeInput::Node(ToDefaultNode),
// TODO: which would be a new node that implements the Default trait (i.e. `Default::default()`)
self.guess_type_from_node(&mut network_path.to_vec(), node_id, input_connector.input_index())
self.guess_type_from_node(node_id, input_connector.input_index(), network_path);
}
pub fn compiled_output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<&Type> {
let (sni, input_index) = self.downstream_caller_from_output(output_connector, network_path)?;
let protonode_input_types = self.resolved_types.get(sni)?;
protonode_input_types.get(*input_index)
}
pub fn output_type(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) {
if let Some(output_type) = self.compiled_output_type(output_connector, network_path) {
return (output_type.clone(), TypeSource::Compiled);
}
(concrete!(()), TypeSource::Error("Not compiled"))
}
pub fn add_type(&mut self, sni: SNI, input_types: Vec<Type>) {
self.resolved_types.insert(sni, input_types);
}
pub fn remove_type(&mut self, sni: SNI) {
self.resolved_types.remove(sni);
}
pub fn set_node_caller(&mut self, node: &NodeId, caller: CompiledProtonodeInput, network_path: &[NodeId]) {
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
return;
};
metadata.transient_metadata.caller = Some(caller);
}
pub fn set_input_caller(&mut self, input_connector: &InputConnector, caller: CompiledProtonodeInput, network_path: &[NodeId]) {
match input_connector {
InputConnector::Node { node_id, input_index } => {
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
return;
};
let Some(input_metadata) = metadata.persistent_metadata.input_metadata.get_mut(*input_index) else {
log::error!("input metadata must exist when setting input caller for node {}, input index {}", node_id, input_index);
return;
};
input_metadata.transient_metadata.caller = Some(caller);
}
InputConnector::Export(export_index) => {
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
return;
};
network_metadata.transient_metadata.callers.resize(*export_index + 1, None);
network_metadata.transient_metadata.callers[*export_index] = Some(caller);
}
}
}
pub fn valid_input_types(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<Type> {
@@ -1496,10 +1579,7 @@ impl NodeNetworkInterface {
let mut node_metadata = DocumentNodeMetadata::default();
node.inputs = old_node.inputs;
node.manual_composition = old_node.manual_composition;
node.visible = old_node.visible;
node.skip_deduplication = old_node.skip_deduplication;
node.original_location = old_node.original_location;
node_metadata.persistent_metadata.display_name = old_node.alias;
node_metadata.persistent_metadata.reference = if old_node.name.is_empty() { None } else { Some(old_node.name) };
node_metadata.persistent_metadata.has_primary_output = old_node.has_primary_output;
@@ -1539,7 +1619,7 @@ impl NodeNetworkInterface {
network: node_network,
network_metadata,
document_metadata: DocumentMetadata::default(),
resolved_types: ResolvedDocumentNodeTypes::default(),
resolved_types: HashMap::new(),
transaction_status: TransactionStatus::Finished,
}
}
@@ -6060,94 +6140,6 @@ pub enum ImportOrExport {
Export(usize),
}
/// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum InputConnector {
#[serde(rename = "node")]
Node {
#[serde(rename = "nodeId")]
node_id: NodeId,
#[serde(rename = "inputIndex")]
input_index: usize,
},
#[serde(rename = "export")]
Export(usize),
}
impl Default for InputConnector {
fn default() -> Self {
InputConnector::Export(0)
}
}
impl InputConnector {
pub fn node(node_id: NodeId, input_index: usize) -> Self {
InputConnector::Node { node_id, input_index }
}
pub fn input_index(&self) -> usize {
match self {
InputConnector::Node { input_index, .. } => *input_index,
InputConnector::Export(input_index) => *input_index,
}
}
pub fn node_id(&self) -> Option<NodeId> {
match self {
InputConnector::Node { node_id, .. } => Some(*node_id),
_ => None,
}
}
}
/// Represents an output connector
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum OutputConnector {
#[serde(rename = "node")]
Node {
#[serde(rename = "nodeId")]
node_id: NodeId,
#[serde(rename = "outputIndex")]
output_index: usize,
},
#[serde(rename = "import")]
Import(usize),
}
impl Default for OutputConnector {
fn default() -> Self {
OutputConnector::Import(0)
}
}
impl OutputConnector {
pub fn node(node_id: NodeId, output_index: usize) -> Self {
OutputConnector::Node { node_id, output_index }
}
pub fn index(&self) -> usize {
match self {
OutputConnector::Node { output_index, .. } => *output_index,
OutputConnector::Import(output_index) => *output_index,
}
}
pub fn node_id(&self) -> Option<NodeId> {
match self {
OutputConnector::Node { node_id, .. } => Some(*node_id),
_ => None,
}
}
pub fn from_input(input: &NodeInput) -> Option<Self> {
match input {
NodeInput::Network { import_index, .. } => Some(Self::Import(*import_index)),
NodeInput::Node { node_id, output_index, .. } => Some(Self::node(*node_id, *output_index)),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct Ports {
input_ports: Vec<(usize, ClickTarget)>,
@@ -6381,6 +6373,7 @@ pub struct NodeNetworkTransientMetadata {
pub rounded_network_edge_distance: TransientMetadata<NetworkEdgeDistance>,
// Wires from the exports
pub wires: Vec<TransientMetadata<WirePathUpdate>>,
pub callers: Vec<Option<CompiledProtonodeInput>>,
}
#[derive(Debug, Clone)]
@@ -6568,8 +6561,8 @@ impl InputPersistentMetadata {
#[derive(Debug, Clone, Default)]
struct InputTransientMetadata {
wire: TransientMetadata<WirePathUpdate>,
// downstream_protonode: populated for all inputs after each compile
// types: populated for each protonode after each
caller: Option<CompiledProtonodeInput>,
input_type: Option<Type>,
}
// TODO: Eventually remove this migration document upgrade code
@@ -6883,6 +6876,8 @@ pub struct DocumentNodeTransientMetadata {
pub click_targets: TransientMetadata<DocumentNodeClickTargets>,
// Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.
pub node_type_metadata: NodeTypeTransientMetadata,
// Stores the caller input since it will be reached through an upstream traversal, but all data is stored per input.
pub caller: Option<CompiledProtonodeInput>,
}
#[derive(Debug, Clone)]

View File

@@ -2,7 +2,8 @@ use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use super::network_interface::NodeNetworkInterface;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::DVec2;
use graph_craft::document::{NodeId, NodeNetwork};
use graph_craft::document::NodeNetwork;
use graphene_std::uuid::NodeId;
use serde::ser::SerializeStruct;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]

View File

@@ -1,11 +1,17 @@
use std::sync::Arc;
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
use super::utility_types::PanelType;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*;
use graphene_std::Color;
use crate::node_graph_executor::CompilationResponse;
use graph_craft::document::CompilationMetadata;
use graphene_std::raster::Image;
use graphene_std::renderer::RenderMetadata;
use graphene_std::text::Font;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::{Color, IntrospectMode};
#[impl_message(Message, Portfolio)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -18,8 +24,24 @@ pub enum PortfolioMessage {
#[child]
Spreadsheet(SpreadsheetMessage),
// Messages
Init,
// Sends a request to compile the network. Should occur when any value, preference, or font changes
CompileActiveDocument,
// Sends a request to evaluate the network. Should occur when any context value changes.2
EvaluateActiveDocument,
// Processes the compilation response and updates the data stored in the network interface for the active document
// TODO: Add document ID in response for stability
ProcessCompilationResponse {
compilation_metadata: CompilationMetadata,
},
ProcessEvaluationResponse {
evaluation_metadata: RenderMetadata,
#[serde(skip)]
introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
},
ProcessThumbnails {
inputs_to_render: HashSet<CompiledProtonodeInput>,
},
DocumentPassMessage {
document_id: DocumentId,
message: DocumentMessage,
@@ -48,7 +70,6 @@ pub enum PortfolioMessage {
document_id: DocumentId,
},
DestroyAllDocuments,
EditorPreferences,
FontLoaded {
font_family: String,
font_style: String,
@@ -120,13 +141,7 @@ pub enum PortfolioMessage {
bounds: ExportBounds,
transparent_background: bool,
},
SubmitActiveGraphRender,
SubmitGraphRender {
document_id: DocumentId,
ignore_hash: bool,
},
ToggleRulers,
UpdateDocumentWidgets,
UpdateOpenDocumentsList,
UpdateVelloPreference,
}

View File

@@ -3,19 +3,18 @@ use super::document::utility_types::network_interface;
use super::spreadsheet::SpreadsheetMessageHandler;
use super::utility_types::{PanelType, PersistentData};
use crate::application::generate_uuid;
use crate::consts::DEFAULT_DOCUMENT_NAME;
use crate::messages::animation::TimingInformation;
use crate::consts::{DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX};
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document_migration::*;
use crate::messages::portfolio::spreadsheet::{InspectInputConnector, SpreadsheetMessageHandlerData};
use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
@@ -45,6 +44,7 @@ pub struct PortfolioMessageHandler {
active_panel: PanelType,
pub(crate) active_document_id: Option<DocumentId>,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
// Data that persists between documents
pub persistent_data: PersistentData,
pub executor: NodeGraphExecutor,
pub selection_mode: SelectionMode,
@@ -52,6 +52,11 @@ pub struct PortfolioMessageHandler {
pub spreadsheet: SpreadsheetMessageHandler,
device_pixel_ratio: Option<f64>,
pub reset_node_definitions_on_open: bool,
// Data from the node graph. Data for inputs are set to be collected on each evaluation, and added on the evaluation response
// Data from old nodes get deleted after a compilation
pub introspected_input_data: HashMap<CompiledProtonodeInput, Box<dyn std::any::Any + Send + Sync>>,
pub downcasted_input_data: HashMap<CompiledProtonodeInput, TaggedValue>,
pub context_data: HashMap<CompiledProtonodeInput, Context>,
}
#[message_handler_data]
@@ -100,7 +105,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
self.menu_bar_message_handler.process_message(message, responses, ());
}
PortfolioMessage::Spreadsheet(message) => {
self.spreadsheet.process_message(message, responses, ());
self.spreadsheet.process_message(message, responses, SpreadsheetMessageHandlerData {introspected_data});
}
PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id {
@@ -109,7 +114,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_id,
ipp,
persistent_data: &self.persistent_data,
executor: &mut self.executor,
current_tool,
preferences,
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
@@ -143,7 +147,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_id,
ipp,
persistent_data: &self.persistent_data,
executor: &mut self.executor,
current_tool,
preferences,
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
@@ -331,25 +334,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
data,
} => {
let font = Font::new(font_family, font_style);
self.persistent_data.font_cache.insert(font, preview_url, data);
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
for document_id in self.document_ids.iter() {
let inspect_node = self.inspect_node_id();
let _ = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
ipp.viewport_bounds.size().as_uvec2(),
timing_information,
inspect_node,
true,
);
}
if self.active_document_mut().is_some() {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
let mut font_cache = self.persistent_data.font_cache.as_ref().clone();
font_cache.insert(font, preview_url, data);
self.persistent_data.font_cache = Arc::new(font_cache);
responses.add(PortfolioMessage::CompileActiveDocument);
}
PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),
PortfolioMessage::Import => {
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages
responses.add(FrontendMessage::TriggerImport);
@@ -448,7 +437,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_migration_upgrades(&mut document, reset_node_definitions_on_open);
// Ensure each node has the metadata for its inputs
for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {
for (mut path, node) in document.network_interface.document_network().clone().recursive_nodes() {
let node_id = path.pop().unwrap();
document.network_interface.validate_input_metadata(node_id, node, &path);
document.network_interface.validate_display_name_metadata(node_id, &path);
document.network_interface.validate_output_names(node_id, node, &path);
@@ -510,7 +500,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
for entry in self.copy_buffer[clipboard as usize].iter().rev() {
paste(entry, responses, &mut all_new_ids)
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids });
}
PortfolioMessage::PasteSerializedData { data } => {
@@ -539,9 +529,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
layers.push(layer);
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids });
responses.add(Message::StartBuffer);
// responses.add(Message::StartBuffer);
responses.add(PortfolioMessage::CenterPastedLayers { layers });
}
}
@@ -648,7 +638,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
}
PortfolioMessage::PasteImage {
@@ -674,12 +664,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
if create_document {
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true });
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
}
}
@@ -706,12 +696,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
if create_document {
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true });
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
}
}
@@ -758,7 +748,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(BroadcastEvent::ToolAbort);
responses.add(BroadcastEvent::SelectionChanged);
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });
if node_graph_open {
responses.add(NodeGraphMessage::UpdateGraphBarRight);
@@ -777,14 +767,332 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(PropertiesPanelMessage::Clear);
}
}
PortfolioMessage::SubmitDocumentExport {
PortfolioMessage::CompileActiveDocument => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", document_id);
return;
};
if document.network_interface.hash_changed() {
self.executor.submit_node_graph_compilation(CompilationRequest {
network: document.network_interface.document_network().clone(),
font_cache: self.persistent_data.font_cache.clone(),
editor_metadata: EditorMetadata {
#[cfg(any(feature = "resvg", feature = "vello"))]
use_vello: preferences.use_vello(),
#[cfg(not(any(feature = "resvg", feature = "vello")))]
use_vello: false,
hide_artboards: false,
for_export: false,
view_mode: document.view_mode,
transform_to_viewport: true,
},
});
}
// Always evaluate after a recompile
responses.add(PortfolioMessage::EvaluateActiveDocument);
}
PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
for (AbsoluteInputConnector { network_path, connector }, caller) in compilation_metadata.protonode_callers_for_value {
document.network_interface.set_input_caller(connector, caller, &network_path)
}
for (protonode_path, caller) in compilation_metadata.protonode_callers_for_node {
let (node_id, network_path) = protonode_path.to_vec().split_last().expect("Protonode path cannot be empty");
document.network_interface.set_node_caller(node_id, caller, &network_path)
}
for (sni, input_types) in compilation_metadata.types_to_add {
document.network_interface.add_type(sni, input_types);
}
for ((sni, number_of_inputs)) in compilation_metadata.types_to_remove {
// Removed saves type of the document node
document.network_interface.remove_type(sni);
// Remove introspection data for all monitor nodes and the thumbnails
let mut cleared_thumbnails = Vec::new();
for monitor_index in 0..number_of_inputs {
self.introspected_input_data.remove((sni, monitor_index));
self.downcasted_input_data.remove((sni, monitor_index));
self.context_data.remove((sni, monitor_index));
cleared_thumbnails.push(NodeId(sni.0+monitor_index as u64 +1));
}
responses.add(FrontendMessage::UpdateThumbnails { add: Vec::new(), clear: cleared_thumbnails })
}
}
PortfolioMessage::EvaluateActiveDocument => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
// Get all the inputs to save data for. This includes vector modify, thumbnails, and spreadsheet data
let inputs_to_monitor = HashSet::new();
let inputs_to_render = HashSet::new();
let inspect_input = None;
// Get the protonode input for all side layer inputs connected to the export in the document network for thumbnails in the layer panel
for caller in document
.network_interface
.document_metadata()
.all_layers()
.filter_map(|layer| {
let input = InputConnector::Node {
node_id: layer.to_node(),
input_index: 1,
};
document
.network_interface
.downstream_caller_from_input(&input, &[])
}) {
inputs_to_monitor.insert((*caller, IntrospectMode::Data));
inputs_to_render.insert(*caller);
}
// Save data for all inputs in the viewed node graph
if document.graph_view_overlay_open {
let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else {
return;
};
for (export_index, export) in viewed_network.exports.iter().enumerate() {
if let Some(caller) = document
.network_interface
.downstream_caller_from_input(InputConnector::Export(export_index), &document.breadcrumb_network_path)
{
inputs_to_monitor.push((*caller, IntrospectMode::Data))
};
if let Some(NodeInput::Node { node_id, .. }) = export {
for upstream_node in document
.network_interface
.upstream_flow_back_from_nodes(vec![*node_id], &document.breadcrumb_network_path, network_interface::FlowType::UpstreamFlow)
{
let node = viewed_network.nodes[&upstream_node];
for (index, _) in node.inputs.iter().enumerate().filter(|(_, node_input)| node_input.is_exposed()) {
if let Some(caller) = document
.network_interface
.downstream_caller_from_input(InputConnector::Node(node_id, index), &document.breadcrumb_network_path)
{
inputs_to_monitor.insert((*caller, IntrospectMode::Data));
inputs_to_render.insert(*caller);
};
}
}
}
}
}
// Save vector data for all path/transform nodes in the document network
match document.network_interface.input_from_connector(&InputConnector::Export(0), &[]) {
Some(NodeInput::Node { node_id, .. }) => {
for upstream_node in document.network_interface.upstream_flow_back_from_nodes(vec![*node_id], &[], network_interface::FlowType::UpstreamFlow) {
let reference = document.network_interface.reference(node_id, &[]).unwrap_or_default().as_deref().unwrap_or_default();
if reference == "Path" || reference == "Transform" {
let input_connector = InputConnector::Node { node_id, input_index: 0 };
let Some(downstream_caller) = document.network_interface.downstream_caller_from_input(&input_connector, &[]) else{
log::error!("could not get downstream caller for node : {:?}", node_id);
continue;
};
inputs_to_monitor.push(*downstream_caller)
}
}
},
_ => {},
}
// Introspect data for the currently selected node (eventually thumbnail) if the spreadsheet view is open
if self.spreadsheet.spreadsheet_view_open {
let selected_network_path = &document.selection_network_path;
// TODO: Replace with selected thumbnail
if let Some(selected_node) = document.network_interface.selected_nodes_in_nested_network(selected_network_path).and_then(|selected_nodes| {
if selected_nodes.0.len() == 1 {
selected_nodes.0.first().copied()
} else {
None
}
}) {
// TODO: Introspect any input rather than just the first input of the selected node
let selected_connector = InputConnector::Node { node_id: selected_node, input_index: 0 };
let Some(caller) = document
.network_interface
.downstream_caller_from_input(&selected_connector, selected_network_path) else {
log::error!("Could not get downstream caller for {:?}", selected_node);
};
inputs_to_monitor.push((*caller, IntrospectMode::Data));
inspect_input = Some(InspectInputConnector { input_connector: AbsoluteInputConnector { network_path: selected_network_path.clone(), connector: selected_connector }, protonode_input: *caller });
}
}
// let animation_time = match animation.timing_information().animation_time {
// AnimationState::Stopped => 0.,
// AnimationState::Playing { start } => ipp.time - start,
// AnimationState::Paused { start, pause_time } => pause_time - start,
// };
let mut context = EditorContext::default();
// context.footprint = Some(Footprint {
// transform: document.metadata().document_to_viewport,
// resolution: ipp.viewport_bounds.size().as_uvec2(),
// quality: RenderQuality::Full,
// });
// context.animation_time = Some(animation_time);
// context.real_time = Some(ipp.time);
// context.downstream_transform = Some(DAffine2::IDENTITY);
let render_config = RenderConfig {
viewport: Footprint {
transform: document.metadata().document_to_viewport,
resolution: ipp.viewport_bounds.size().as_uvec2(),
..Default::default()
},
time: animation.timing_information(),
#[cfg(any(feature = "resvg", feature = "vello"))]
export_format: graphene_std::application_io::ExportFormat::Canvas,
#[cfg(not(any(feature = "resvg", feature = "vello")))]
export_format: graphene_std::application_io::ExportFormat::Svg,
view_mode: document.view_mode,
hide_artboards: false,
for_export: false,
};
context.render_config = render_config;
self.executor.submit_node_graph_evaluation(
context,
inputs_to_monitor,
None,
None,
);
// Queue messages to be run after the evaluation returns data for the inputs to monitor
responses.add(Message::StartQueue);
if let Some(inspect_input) = inspect_input {
responses.add(SpreadsheetMessage::UpdateLayout { inpect_input });
}
responses.add(PortfolioMessage::ProcessThumbnails {inputs_to_render});
responses.add(Message::EndQueue);
}
PortfolioMessage::ProcessEvaluationResponse { evaluation_metadata, introspected_inputs } => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
for (input, mode, data) in introspected_inputs {
match mode {
IntrospectMode::Input => {
let Some(context) = data.downcast_ref()
self.introspected_input_data.extend(introspected_inputs);
},
IntrospectMode::Data => {
self.introspected_input_data.extend(introspected_inputs);
},
}
}
let RenderMetadata {
upstream_footprints: footprints,
local_transforms,
click_targets,
clip_targets,
} = evaluation_metadata;
responses.add(DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints,
local_transforms,
});
responses.add(DocumentMessage::UpdateClickTargets { click_targets });
responses.add(DocumentMessage::UpdateClipTargets { clip_targets });
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
// match document.animation_state {
// AnimationState::Playing { .. } => responses.add(PortfolioMessage::EvaluateActiveDocument),
// _ => {}
// };
},
PortfolioMessage::ProcessThumbnails { inputs_to_render } => {
let mut thumbnail_response = ThumbnailRenderResponse::default();
for thumbnail_input in inputs_to_render {
let monitor_node_id = thumbnail_input.0.0 + thumbnail_input.1 as u64 + 1;
match self.try_render_thumbnail(&thumbnail_input) {
ThumbnailRenderResult::NoChange => {}
ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(NodeId(monitor_node_id)),
ThumbnailRenderResult::UpdateThumbnail(thumbnail) => {
thumbnail_response.add.push((NodeId(monitor_node_id), thumbnail));
},
}
}
responses.add(FrontendMessage::UpdateThumbnails { add: thumbnail_response.add, clear: thumbnail_response.clear })
},
PortfolioMessage::ActiveDocumentExport {
file_name,
file_type,
animation_export_data,
scale_factor,
bounds,
transparent_background,
} => {
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document");
// Update the scope inputs with the render settings
// self.executor.submit_node_graph_compilation(CompilationRequest {
// network: document.network_interface.document_network().clone(),
// font_cache: self.persistent_data.font_cache.clone(),
// editor_metadata: EditorMetadata {
// #[cfg(any(feature = "resvg", feature = "vello"))]
// use_vello: preferences.use_vello(),
// #[cfg(not(any(feature = "resvg", feature = "vello")))]
// use_vello: false,
// hide_artboards: transparent_background,
// for_export: true,
// view_mode: document.view_mode,
// transform_to_viewport: true,
// },
// });
let document_to_viewport = document.metadata().document_to_viewport;
// Calculate the bounding box of the region to be exported
let document_bounds = match bounds {
ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!transparent_background),
ExportBounds::Selection => document.network_interface.selected_bounds_document_space(!transparent_background, &[]),
ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id),
// ExportBounds::Viewport => ipp.document_bounds(document_to_viewport),
}
.ok_or_else(|| "No bounding box".to_string())?;
let size = document_bounds[1] - document_bounds[0];
let scaled_size = size * scale_factor;
let transform = DAffine2::from_translation(document_bounds[0]).inverse();
let mut context = EditorContext::default();
// context.footprint = Footprint {
// document_to_viewport: DAffine2::from_scale(DVec2::splat(scale_factor)) * transform,
// resolution: scaled_size.as_uvec2(),
// ..Default::default()
// };
// context.real_time = Some(ipp.time);
// context.downstream_transform = Some(DAffine2::IDENTITY);
let render_config = RenderConfig {
viewport: Footprint {
transform: DAffine2::from_scale(DVec2::splat(scale_factor)) * transform,
resolution: (size * scale_factor).as_uvec2(),
..Default::default()
},
time: Default::default(),
export_format: graphene_std::application_io::ExportFormat::Svg,
view_mode: document.view_mode,
hide_artboards: transparent_background,
for_export: true,
};
context.render_config = render_config;
// Special handling for exporting the artwork
let file_suffix = &format!(".{file_type:?}").to_lowercase();
let file_name = match file_name.ends_with(FILE_SAVE_SUFFIX) {
true => file_name.replace(FILE_SAVE_SUFFIX, file_suffix),
false => file_name + file_suffix,
};
let export_config = ExportConfig {
file_name,
file_type,
@@ -793,37 +1101,76 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
transparent_background,
..Default::default()
};
let result = self.executor.submit_document_export(document, export_config);
if let Err(description) = result {
responses.add(DialogMessage::DisplayDialogError {
title: "Unable to export document".to_string(),
description,
});
}
}
PortfolioMessage::SubmitActiveGraphRender => {
if let Some(document_id) = self.active_document_id {
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
}
}
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
let inspect_node = self.inspect_node_id();
let result = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
ipp.viewport_bounds.size().as_uvec2(),
timing_information,
inspect_node,
ignore_hash,
);
self.executor.submit_node_graph_evaluation(
context,
Vec::new(),
None,
Some(ExportConfig {
file_name,
file_type,
scale_factor,
bounds,
transparent_background,
size: scaled_size,
}),
);
if let Err(description) = result {
responses.add(DialogMessage::DisplayDialogError {
title: "Unable to update node graph".to_string(),
description,
});
}
// if let Some((start, end, fps)) = animation_export_data {
// let total_frames = ((start - end) * fps) as u32;
// for frame_index in 0..total_frames {
// context.animation_time = Some(start + (frame_index as f64) / fps);
// self.executor.submit_node_graph_evaluation(
// context.clone(),
// Vec::new(),
// None,
// Some(ExportConfig {
// file_name,
// save_render: frame_index == (total_frames - 1),
// file_type,
// size: scaled_size,
// fps: Some(fps),
// }),
// );
// }
// } else {
// let animation_time = match document.animation_state {
// AnimationState::Stopped => 0.,
// AnimationState::Playing { start } => start,
// AnimationState::Paused { start, pause_time } => pause_time,
// };
// context.animation_time = Some(animation_time);
// self.executor.submit_node_graph_evaluation(
// EditorEvaluationMetadata {
// inputs_to_monitor: Vec::new(),
// context,
// custom_node_to_evaluate: None,
// },
// Some(ExportConfig {
// file_name,
// file_type,
// size: scaled_size,
// }),
// );
// }
// Reset the scope nodes for hide artboards/hide_artboard name
// self.executor.submit_node_graph_compilation(CompilationRequest {
// network: document.network_interface.document_network().clone(),
// font_cache: self.persistent_data.font_cache.clone(),
// editor_metadata: EditorMetadata {
// #[cfg(any(feature = "resvg", feature = "vello"))]
// use_vello: preferences.use_vello().use_vello,
// #[cfg(not(any(feature = "resvg", feature = "vello")))]
// use_vello: false,
// hide_artboards: false,
// for_export: false,
// view_mode: document.view_mode,
// transform_to_viewport: true,
// },
// });
}
PortfolioMessage::ToggleRulers => {
if let Some(document) = self.active_document_mut() {
document.rulers_visible = !document.rulers_visible;
@@ -834,7 +1181,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::UpdateDocumentWidgets => {
if let Some(document) = self.active_document() {
document.update_document_widgets(responses, animation.is_playing(), timing_information.animation_time);
document.update_document_widgets(responses, animation.is_playing(), animation_time);
}
}
PortfolioMessage::UpdateOpenDocumentsList => {
@@ -853,10 +1200,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
.collect::<Vec<_>>();
responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents });
}
PortfolioMessage::UpdateVelloPreference => {
responses.add(NodeGraphMessage::RunDocumentGraph);
self.persistent_data.use_vello = preferences.use_vello;
}
}
}
@@ -990,29 +1333,68 @@ impl PortfolioMessageHandler {
/text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string();
responses.add(Message::EndBuffer {
render_metadata: graphene_std::renderer::RenderMetadata::default(),
});
responses.add(Message::ProcessQueue((graphene_std::renderer::EvaluationMetadata::default(), Vec::new())));
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
}
result
}
/// Get the id of the node that should be used as the target for the spreadsheet
pub fn inspect_node_id(&self) -> Option<NodeId> {
// Spreadsheet not open, skipping
if !self.spreadsheet.spreadsheet_view_open {
return None;
// Returns an error if the data could not be introspected, returns None if the data type could not be rendered.
fn try_render_thumbnail(&self, protonode_input: &CompiledProtonodeInput) -> ThumbnailRenderResult {
let Ok(introspected_data) = self.introspected_input_data.get(protonode_input) else {
log::error!("Could not introspect node from input: {:?}", protonode_input);
return ThumbnailRenderResult::ClearThumbnail;
};
if let Some(previous_tagged_value) = self.downcasted_input_data.get(protonode_input) {
if previous_tagged_value.compare_value_to_dyn_any(introspected_data) {
return ThumbnailRenderResult::NoChange;
}
}
let Ok(new_tagged_value) = TaggedValue::try_from_std_any_ref(&introspected_data) else {
return ThumbnailRenderResult::ClearThumbnail;
};
let Some(renderable_data) = TaggedValue::as_renderable(&new_tagged_value) else {
// New value is not renderable
return ThumbnailRenderResult::ClearThumbnail;
};
let document = self.documents.get(&self.active_document_id?)?;
let selected_nodes = document.network_interface.selected_nodes().0;
let render_params = RenderParams {
view_mode: ViewMode::Normal,
culling_bounds: bounds,
thumbnail: true,
hide_artboards: false,
for_export: false,
for_mask: false,
alignment_parent_transform: None,
};
// Selected nodes != 1, skipping
if selected_nodes.len() != 1 {
return None;
}
// Render the thumbnail data into an SVG string
let mut render = SvgRender::new();
renderable_data.render_svg(&mut render, &render_params);
selected_nodes.first().copied()
// Give the SVG a viewbox and outer <svg>...</svg> wrapper tag
let [min, max] = renderable_data.bounding_box(DAffine2::IDENTITY, true).unwrap_or_default();
render.format_svg(min, max);
self.downcasted_input_data.insert(protonode_input, new_tagged_value);
ThumbnailRenderResult::UpdateThumbnail(render.svg.to_svg_string())
}
}
#[derive(Clone, Debug, Default)]
pub struct ThumbnailRenderResponse {
add: Vec<(SNI, String)>,
clear: Vec<SNI>,
}
pub enum ThumbnailRenderResult {
NoChange,
// Cleared if there is an error or the data could not be rendered
ClearThumbnail,
UpdateThumbnail(String),
}

View File

@@ -1,5 +1,5 @@
use crate::messages::prelude::*;
use crate::node_graph_executor::InspectResult;
use graph_craft::document::AbsoluteInputConnector;
use graphene_std::uuid::CompiledProtonodeInput;
/// The spreadsheet UI allows for instance data to be previewed.
#[impl_message(Message, PortfolioMessage, Spreadsheet)]
@@ -7,27 +7,26 @@ use crate::node_graph_executor::InspectResult;
pub enum SpreadsheetMessage {
ToggleOpen,
UpdateLayout {
#[serde(skip)]
inspect_result: InspectResult,
},
UpdateLayout { inpect_input: InspectInputConnector },
PushToInstancePath {
index: usize,
},
TruncateInstancePath {
len: usize,
},
PushToInstancePath { index: usize },
TruncateInstancePath { len: usize },
ViewVectorDataDomain {
domain: VectorDataDomain,
},
ViewVectorDataDomain { domain: VectorDataDomain },
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug)]
pub enum VectorDataDomain {
#[default]
Points,
Segments,
Regions,
}
/// The mapping of input where the data is extracted from to the selected input to display data for
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub struct InspectInputConnector {
pub input_connector: AbsoluteInputConnector,
pub protonode_input: CompiledProtonodeInput,
}

View File

@@ -1,63 +1,74 @@
use super::VectorDataDomain;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout};
use crate::messages::portfolio::spreadsheet::InspectInputConnector;
use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::NodeId;
use graph_craft::document::{AbsoluteInputConnector, NodeId};
use graphene_std::Color;
use graphene_std::Context;
use graphene_std::GraphicGroupTable;
use graphene_std::instances::Instances;
use graphene_std::memo::IORecord;
use graphene_std::raster::Image;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement};
use std::any::Any;
use std::sync::Arc;
pub struct SpreadsheetMessageHandlerData {
pub introspected_data: &HashMap<CompiledProtonodeInput, Box<dyn std::any::Any + Send + Sync>>;
}
/// The spreadsheet UI allows for instance data to be previewed.
#[derive(Default, Debug, Clone, ExtractField)]
pub struct SpreadsheetMessageHandler {
/// Sets whether or not the spreadsheet is drawn.
pub spreadsheet_view_open: bool,
inspect_node: Option<NodeId>,
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
inspect_input: Option<InspectInputConnector>,
// Downcasted data is not saved because the spreadsheet is simply a window into the data flowing through the input
// introspected_data: Option<TaggedValue>,
instances_path: Vec<usize>,
viewing_vector_data_domain: VectorDataDomain,
}
#[message_handler_data]
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _: ()) {
impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData> for SpreadsheetMessageHandler {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, data: SpreadsheetMessageHandlerData) {
let {introspected_data} = data;
match message {
SpreadsheetMessage::ToggleOpen => {
self.spreadsheet_view_open = !self.spreadsheet_view_open;
// Run the graph to grab the data
if self.spreadsheet_view_open {
responses.add(NodeGraphMessage::RunDocumentGraph);
// TODO: This will not get always get data since the input could be cached, and the monitor node would not
// Be run on the evaluation. To solve this, pass in an AbsoluteNodeInput as a parameter to the compilation which tells the compiler
// to generate a random SNI in order to reset any downstream cache
// Run the graph to grab the data
responses.add(PortfolioMessage::EvaluateActiveDocument);
}
// Update checked UI state for open
responses.add(MenuBarMessage::SendLayout);
self.update_layout(responses);
}
SpreadsheetMessage::UpdateLayout { mut inspect_result } => {
self.inspect_node = Some(inspect_result.inspect_node);
self.introspected_data = inspect_result.take_data();
self.update_layout(responses)
// Queued on introspection request, runs on introspection response when the data has been sent back to the editor
SpreadsheetMessage::UpdateLayout { inpect_input } => {
self.inspect_input = Some(inpect_input);
self.update_layout(introspected_data, responses);
}
SpreadsheetMessage::PushToInstancePath { index } => {
self.instances_path.push(index);
self.update_layout(responses);
self.update_layout(introspected_data, responses);
}
SpreadsheetMessage::TruncateInstancePath { len } => {
self.instances_path.truncate(len);
self.update_layout(responses);
self.update_layout(introspected_data, responses);
}
SpreadsheetMessage::ViewVectorDataDomain { domain } => {
self.viewing_vector_data_domain = domain;
self.update_layout(responses);
self.update_layout(introspected_data, responses);
}
}
}
@@ -68,9 +79,10 @@ impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
}
impl SpreadsheetMessageHandler {
fn update_layout(&mut self, responses: &mut VecDeque<Message>) {
fn update_layout(&mut self, introspected_data: &HashMap<CompiledProtonodeInput, Box<dyn std::any::Any + Send + Sync>>, responses: &mut VecDeque<Message>) {
responses.add(FrontendMessage::UpdateSpreadsheetState {
node: self.inspect_node,
// The node is sent when the data is available
node: None,
open: self.spreadsheet_view_open,
});
if !self.spreadsheet_view_open {
@@ -82,12 +94,20 @@ impl SpreadsheetMessageHandler {
breadcrumbs: Vec::new(),
vector_data_domain: self.viewing_vector_data_domain,
};
let mut layout = self
.introspected_data
.as_ref()
.map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data))
.unwrap_or_else(|| Some(label("No data")))
.unwrap_or_else(|| label("Failed to downcast data"));
let mut layout = match self.inspect_input {
Some(inspect_input) => {
match introspected_data.get(&inspect_input.protonode_input){
Some(data) => {
match generate_layout(instrospected_data, &mut layout_data) {
Some(layout) => layout,
None => label("The introspected data is not a supported type to be displayed."),
}
},
None => label("Introspected data is not available for this input. This input may be cached."),
}
},
None => label("No input selected to show data for."),
};
if layout_data.breadcrumbs.len() > 1 {
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
@@ -110,21 +130,15 @@ struct LayoutData<'a> {
vector_data_domain: VectorDataDomain,
}
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
fn generate_layout(introspected_data: &Box<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// We simply try random types. TODO: better strategy.
#[allow(clippy::manual_map)]
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, ArtboardGroupTable>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), ArtboardGroupTable>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, GraphicGroupTable>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), GraphicGroupTable>>() {
Some(io.output.layout_with_breadcrumb(data))
if let Some(io) = introspected_data.downcast_ref::<ArtboardGroupTable>() {
Some(io.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<VectorDataTable>() {
Some(io.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<GraphicGroupTable>() {
Some(io.layout_with_breadcrumb(data))
} else {
None
}

View File

@@ -2,8 +2,7 @@ use graphene_std::text::FontCache;
#[derive(Debug, Default)]
pub struct PersistentData {
pub font_cache: FontCache,
pub use_vello: bool,
pub font_cache: Arc<FontCache>,
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]

View File

@@ -53,8 +53,6 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
if let Ok(deserialized_preferences) = serde_json::from_str::<PreferencesMessageHandler>(&preferences) {
*self = deserialized_preferences;
responses.add(PortfolioMessage::EditorPreferences);
responses.add(PortfolioMessage::UpdateVelloPreference);
responses.add(PreferencesMessage::ModifyLayout {
zoom_with_scroll: self.zoom_with_scroll,
});
@@ -70,8 +68,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
// Per-preference messages
PreferencesMessage::UseVello { use_vello } => {
self.use_vello = use_vello;
responses.add(PortfolioMessage::UpdateVelloPreference);
responses.add(PortfolioMessage::EditorPreferences);
responses.add(PortfolioMessage::CompileActiveDocument);
}
PreferencesMessage::VectorMeshes { enabled } => {
self.vector_meshes = enabled;

View File

@@ -204,6 +204,6 @@ impl NumberOfPointsDial {
input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
}

View File

@@ -450,6 +450,6 @@ impl PointRadiusHandle {
input_connector: InputConnector::node(node_id, radius_index),
input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
}

View File

@@ -13,6 +13,7 @@ use graphene_std::NodeInputDecleration;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::Gradient;
use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType};
use std::collections::VecDeque;
@@ -152,8 +153,8 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
parent: first_layer,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::StartBuffer);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartQueue);
responses.add(PenToolMessage::RecalculateLatestPointsPosition);
}

View File

@@ -78,7 +78,7 @@ impl Line {
input_connector: InputConnector::node(node_id, 2),
input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
pub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) {

View File

@@ -378,8 +378,8 @@ impl Fsm for BrushToolFsmState {
// Create the new layer, wait for the render output to return its transform, and then create the rest of the layer
else {
new_brush_layer(document, responses);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::StartBuffer);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartQueue);
responses.add(BrushToolMessage::DragStart);
BrushToolFsmState::Ready
}

View File

@@ -251,7 +251,7 @@ impl Fsm for FreehandToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
tool_data.layer = Some(layer);

View File

@@ -1258,7 +1258,7 @@ impl PenToolData {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
// This causes the following message to be run only after the next graph evaluation runs and the transforms are updated
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
// It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky)
responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport });
}
@@ -2085,7 +2085,7 @@ impl Fsm for PenToolFsmState {
node_ids: vec![layer.to_node()],
delete_children: true,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
} else if (latest_points && tool_data.prior_segment_endpoint.is_none())
|| (tool_data.prior_segment_endpoint.is_some() && tool_data.prior_segment_layer != Some(layer) && latest_points)
{
@@ -2144,7 +2144,7 @@ impl Fsm for PenToolFsmState {
node_ids: vec![layer.unwrap().to_node()],
delete_children: true,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
responses.add(OverlaysMessage::Draw);

View File

@@ -517,7 +517,7 @@ impl SelectToolData {
}
let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
self.layers_dragging = new_dragging;
}
@@ -555,7 +555,7 @@ impl SelectToolData {
})
.collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
self.layers_dragging = original;

View File

@@ -491,7 +491,7 @@ impl Fsm for ShapeToolFsmState {
input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::U32(n + 1), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
self
@@ -520,7 +520,7 @@ impl Fsm for ShapeToolFsmState {
input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::U32((n - 1).max(3)), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
self
@@ -599,7 +599,7 @@ impl Fsm for ShapeToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses);
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
match tool_data.current_shape {
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => {

View File

@@ -360,7 +360,7 @@ impl Fsm for SplineToolFsmState {
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
tool_data.current_layer = Some(layer);
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
SplineToolFsmState::Drawing
}

View File

@@ -298,7 +298,7 @@ impl TextToolData {
node_ids: vec![self.layer.to_node()],
delete_children: true,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
TextToolFsmState::Ready
}
@@ -362,7 +362,7 @@ impl TextToolData {
input_connector: InputConnector::node(graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(), 1),
input: NodeInput::value(TaggedValue::String("".to_string()), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
};
}
@@ -381,7 +381,7 @@ impl TextToolData {
parent: document.new_layer_parent(true),
insert_index: 0,
});
responses.add(Message::StartBuffer);
responses.add(Message::StartQueue);
responses.add(GraphOperationMessage::FillSet {
layer: self.layer,
fill: if editing_text.color.is_some() {
@@ -402,7 +402,7 @@ impl TextToolData {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option<LayerNodeIdentifier> {
@@ -649,7 +649,7 @@ impl Fsm for TextToolFsmState {
skip_rerender: false,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
// Auto-panning
let messages = [
@@ -710,7 +710,7 @@ impl Fsm for TextToolFsmState {
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
// Auto-panning
let messages = [
@@ -830,7 +830,7 @@ impl Fsm for TextToolFsmState {
input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1),
input: NodeInput::value(TaggedValue::String(tool_data.new_text.clone()), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
TextToolFsmState::Ready
} else {

View File

@@ -291,7 +291,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
update_colinear_handles(&selected_layers, document, responses);
responses.add(DocumentMessage::EndTransaction);
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
if using_path_tool {

View File

@@ -1,71 +1,94 @@
use std::sync::Arc;
use crate::consts::FILE_SAVE_SUFFIX;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::prelude::*;
use glam::{DAffine2, DVec2, UVec2};
use graph_craft::document::value::{RenderOutput, TaggedValue};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, generate_uuid};
use dyn_any::DynAny;
use glam::DAffine2;
use graph_craft::document::value::{NetworkOutput, TaggedValue};
use graph_craft::document::{
AbsoluteInputConnector, AbsoluteOutputConnector, CompilationMetadata, CompiledNodeMetadata, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, generate_uuid,
};
use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences;
use graphene_std::application_io::TimingInformation;
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig};
use graphene_std::renderer::RenderSvgSegmentList;
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender};
use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
use graph_craft::wasm_application_io::{EditorCompilationMetadata, EditorEvaluationMetadata, EditorMetadata};
use graphene_std::application_io::{CompilationMetadata, TimingInformation};
use graphene_std::application_io::{EditorEvaluationMetadata, NodeGraphUpdateMessage};
use graphene_std::memo::IntrospectMode;
use graphene_std::renderer::{EvaluationMetadata, format_transform_matrix};
use graphene_std::renderer::{RenderMetadata, RenderSvgSegmentList};
use graphene_std::renderer::{RenderParams, SvgRender};
use graphene_std::text::FontCache;
use graphene_std::transform::Footprint;
use graphene_std::transform::{Footprint, RenderQuality};
use graphene_std::uuid::{CompiledProtonodeInput, ProtonodePath, SNI};
use graphene_std::vector::VectorData;
use graphene_std::vector::style::ViewMode;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
use graphene_std::wasm_application_io::NetworkOutput;
use graphene_std::{CompiledProtonodeInput, OwnedContextImpl, SNI};
mod runtime_io;
use interpreted_executor::dynamic_executor::{EditorContext, ResolvedDocumentNodeMetadata};
pub use runtime_io::NodeRuntimeIO;
mod runtime;
pub use runtime::*;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ExecutionRequest {
execution_id: u64,
render_config: RenderConfig,
#[derive(Clone, Debug, Default, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct CompilationRequest {
pub network: NodeNetwork,
// Data which is avaialable from scope inputs (currently WasmEditorApi, but will be split)
pub font_cache: Arc<FontCache>,
pub editor_metadata: EditorMetadata,
}
#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub struct ExecutionResponse {
execution_id: u64,
result: Result<TaggedValue, String>,
responses: VecDeque<FrontendMessage>,
transform: DAffine2,
vector_modify: HashMap<NodeId, VectorData>,
/// The resulting value from the temporary inspected during execution
inspect_result: Option<InspectResult>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct CompilationResponse {
result: Result<ResolvedDocumentNodeTypesDelta, String>,
result: Result<CompilationMetadata, String>,
node_graph_errors: GraphErrors,
}
#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
// Metadata the editor sends when evaluating the network
#[derive(Debug, Default, DynAny)]
pub struct EvaluationRequest {
pub evaluation_id: u64,
pub inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>,
pub context: EditorContext,
// pub custom_node_to_evaluate: Option<SNI>,
}
// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub struct EvaluationResponse {
evaluation_id: u64,
result: Result<TaggedValue, String>,
introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
// TODO: Handle transforming node graph output in the node graph itself
transform: DAffine2,
}
// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub enum NodeGraphUpdate {
ExecutionResponse(ExecutionResponse),
CompilationResponse(CompilationResponse),
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
EvaluationResponse(EvaluationResponse),
}
#[derive(Debug, Default)]
pub struct NodeGraphExecutor {
runtime_io: NodeRuntimeIO,
futures: HashMap<u64, ExecutionContext>,
node_graph_hash: u64,
old_inspect_node: Option<NodeId>,
futures: HashMap<u64, EvaluationContext>,
}
#[derive(Debug, Clone)]
struct ExecutionContext {
struct EvaluationContext {
export_config: Option<ExportConfig>,
}
impl Default for NodeGraphExecutor {
fn default() -> Self {
Self {
futures: Default::default(),
runtime_io: NodeRuntimeIO::new(),
}
}
}
impl NodeGraphExecutor {
/// A local runtime is useful on threads since having global state causes flakes
#[cfg(test)]
@@ -75,189 +98,54 @@ impl NodeGraphExecutor {
let node_runtime = NodeRuntime::new(request_receiver, response_sender);
let node_executor = Self {
futures: Default::default(),
futures: HashMap::new(),
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
node_graph_hash: 0,
old_inspect_node: None,
};
(node_runtime, node_executor)
}
/// Execute the network by flattening it and creating a borrow stack.
fn queue_execution(&self, render_config: RenderConfig) -> u64 {
let execution_id = generate_uuid();
let request = ExecutionRequest { execution_id, render_config };
self.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)).expect("Failed to send generation request");
execution_id
}
pub fn update_font_cache(&self, font_cache: FontCache) {
self.runtime_io.send(GraphRuntimeRequest::FontCacheUpdate(font_cache)).expect("Failed to send font cache update");
}
pub fn update_editor_preferences(&self, editor_preferences: EditorPreferences) {
self.runtime_io
.send(GraphRuntimeRequest::EditorPreferencesUpdate(editor_preferences))
.expect("Failed to send editor preferences");
}
/// Updates the network to monitor all inputs. Useful for the testing.
#[cfg(test)]
pub(crate) fn update_node_graph_instrumented(&mut self, document: &mut DocumentMessageHandler) -> Result<Instrumented, String> {
// We should always invalidate the cache.
self.node_graph_hash = generate_uuid();
let mut network = document.network_interface.document_network().clone();
let instrumented = Instrumented::new(&mut network);
self.runtime_io
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node: None }))
.send(GraphRuntimeRequest::CompilationRequest(CompilationRequest { network, ..Default::default() }))
.map_err(|e| e.to_string())?;
Ok(instrumented)
}
/// Update the cached network if necessary.
fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, inspect_node: Option<NodeId>, ignore_hash: bool) -> Result<(), String> {
let network_hash = document.network_interface.document_network().current_hash();
// Refresh the graph when it changes or the inspect node changes
if network_hash != self.node_graph_hash || self.old_inspect_node != inspect_node || ignore_hash {
let network = document.network_interface.document_network().clone();
self.old_inspect_node = inspect_node;
self.node_graph_hash = network_hash;
self.runtime_io
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node }))
.map_err(|e| e.to_string())?;
}
Ok(())
/// Compile the network
pub fn submit_node_graph_compilation(&mut self, compilation_request: CompilationRequest) {
self.runtime_io.send(GraphRuntimeRequest::CompilationRequest(compilation_request)).map_err(|e| e.to_string());
}
/// Adds an evaluate request for whatever current network is cached.
pub(crate) fn submit_current_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2, time: TimingInformation) -> Result<(), String> {
let render_config = RenderConfig {
viewport: Footprint {
transform: document.metadata().document_to_viewport,
resolution: viewport_resolution,
..Default::default()
},
time,
#[cfg(any(feature = "resvg", feature = "vello"))]
export_format: graphene_std::application_io::ExportFormat::Canvas,
#[cfg(not(any(feature = "resvg", feature = "vello")))]
export_format: graphene_std::application_io::ExportFormat::Svg,
view_mode: document.view_mode,
hide_artboards: false,
for_export: false,
};
// Execute the node graph
let execution_id = self.queue_execution(render_config);
self.futures.insert(execution_id, ExecutionContext { export_config: None });
Ok(())
}
/// Evaluates a node graph, computing the entire graph
pub fn submit_node_graph_evaluation(
&mut self,
document: &mut DocumentMessageHandler,
viewport_resolution: UVec2,
time: TimingInformation,
inspect_node: Option<NodeId>,
ignore_hash: bool,
) -> Result<(), String> {
self.update_node_graph(document, inspect_node, ignore_hash)?;
self.submit_current_node_graph_evaluation(document, viewport_resolution, time)?;
Ok(())
}
/// Evaluates a node graph for export
pub fn submit_document_export(&mut self, document: &mut DocumentMessageHandler, mut export_config: ExportConfig) -> Result<(), String> {
let network = document.network_interface.document_network().clone();
// Calculate the bounding box of the region to be exported
let bounds = match export_config.bounds {
ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!export_config.transparent_background),
ExportBounds::Selection => document.network_interface.selected_bounds_document_space(!export_config.transparent_background, &[]),
ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id),
}
.ok_or_else(|| "No bounding box".to_string())?;
let size = bounds[1] - bounds[0];
let transform = DAffine2::from_translation(bounds[0]).inverse();
let render_config = RenderConfig {
viewport: Footprint {
transform: DAffine2::from_scale(DVec2::splat(export_config.scale_factor)) * transform,
resolution: (size * export_config.scale_factor).as_uvec2(),
..Default::default()
},
time: Default::default(),
export_format: graphene_std::application_io::ExportFormat::Svg,
view_mode: document.view_mode,
hide_artboards: export_config.transparent_background,
for_export: true,
};
export_config.size = size;
// Execute the node graph
self.runtime_io
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node: None }))
.map_err(|e| e.to_string())?;
let execution_id = self.queue_execution(render_config);
let execution_context = ExecutionContext { export_config: Some(export_config) };
self.futures.insert(execution_id, execution_context);
Ok(())
}
fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> {
let TaggedValue::RenderOutput(RenderOutput {
data: graphene_std::wasm_application_io::RenderOutputType::Svg(svg),
..
}) = node_graph_output
else {
return Err("Incorrect render type for exporting (expected RenderOutput::Svg)".to_string());
};
let ExportConfig {
file_type,
file_name,
size,
scale_factor,
..
} = export_config;
let file_suffix = &format!(".{file_type:?}").to_lowercase();
let name = match file_name.ends_with(FILE_SAVE_SUFFIX) {
true => file_name.replace(FILE_SAVE_SUFFIX, file_suffix),
false => file_name + file_suffix,
};
if file_type == FileType::Svg {
responses.add(FrontendMessage::TriggerDownloadTextFile { document: svg, name });
} else {
let mime = file_type.to_mime().to_string();
let size = (size * scale_factor).into();
responses.add(FrontendMessage::TriggerDownloadImage { svg, name, mime, size });
}
Ok(())
context: EditorContext,
inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>,
custom_node_to_evaluate: Option<SNI>,
export_config: Option<ExportConfig>,
) {
let evaluation_id = generate_uuid();
self.runtime_io.send(GraphRuntimeRequest::EvaluationRequest(editor_evaluation_request)).map_err(|e| e.to_string());
let evaluation_context = EvaluationContext { export_config };
self.futures.insert(evaluation_id, evaluation_context);
}
// Continuously poll the executor (called by request animation frame)
pub fn poll_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Result<(), String> {
let results = self.runtime_io.receive().collect::<Vec<_>>();
for response in results {
// Moved into portfolio message handler, since this is where the introspected inputs are saved
for response in self.runtime_io.receive() {
match response {
NodeGraphUpdate::ExecutionResponse(execution_response) => {
let ExecutionResponse {
execution_id,
result,
responses: existing_responses,
transform,
vector_modify,
inspect_result,
} = execution_response;
NodeGraphUpdate::EvaluationResponse(EvaluationResponse {
evaluation_id,
result,
transform,
introspected_inputs,
}) => {
responses.add(OverlaysMessage::Draw);
let node_graph_output = match result {
@@ -269,51 +157,62 @@ impl NodeGraphExecutor {
return Err(format!("Node graph evaluation failed:\n{e}"));
}
};
responses.extend(existing_responses.into_iter().map(Into::into));
document.network_interface.update_vector_modify(vector_modify);
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
if let Some(export_config) = execution_context.export_config {
// Special handling for exporting the artwork
self.export(node_graph_output, export_config, responses)?
} else {
self.process_node_graph_output(node_graph_output, transform, responses)?
}
// Update the spreadsheet on the frontend using the value of the inspect result.
if self.old_inspect_node.is_some() {
if let Some(inspect_result) = inspect_result {
responses.add(SpreadsheetMessage::UpdateLayout { inspect_result });
let render_output = match node_graph_output {
TaggedValue::RenderOutput(render_output) => render_output,
value => {
return Err("Incorrect render type for exporting (expected NetworkOutput)".to_string());
}
};
let evaluation_context = self.futures.remove(&evaluation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
if let Some(export_config) = evaluation_context.export_config {
// Export
let TaggedValue::RenderOutput(RenderOutput {
data: graphene_std::wasm_application_io::RenderOutputType::Svg(svg),
..
}) = node_graph_output
else {
return Err("Incorrect render type for exporting (expected RenderOutput::Svg)".to_string());
};
match export_config.file_type {
FileType::Svg => {
responses.add(FrontendMessage::TriggerDownloadTextFile {
document: svg,
name: export_config.file_name,
});
}
_ => {
responses.add(FrontendMessage::TriggerDownloadImage {
svg,
name: export_config.file_name,
mime: export_config.file_type.to_mime().to_string(),
size: export_config.size.into(),
});
}
}
} else {
// Update artwork
self.process_node_graph_output(render_output, introspected_inputs, transform, responses);
}
}
NodeGraphUpdate::CompilationResponse(execution_response) => {
let CompilationResponse { node_graph_errors, result } = execution_response;
let type_delta = match result {
NodeGraphUpdate::CompilationResponse(compilation_response) => {
let CompilationResponse { node_graph_errors, result } = compilation_response;
let compilation_metadata = match result {
Err(e) => {
// Clear the click targets while the graph is in an un-renderable state
document.network_interface.update_click_targets(HashMap::new());
document.network_interface.update_vector_modify(HashMap::new());
log::trace!("{e}");
responses.add(NodeGraphMessage::UpdateTypes {
resolved_types: Default::default(),
node_graph_errors,
});
document.node_graph_handler.node_graph_errors = node_graph_errors;
responses.add(NodeGraphMessage::SendGraph);
log::trace!("{e}");
return Err(format!("Node graph evaluation failed:\n{e}"));
}
Ok(result) => result,
};
responses.add(NodeGraphMessage::UpdateTypes {
resolved_types: type_delta,
node_graph_errors,
});
responses.add(PortfolioMessage::ProcessCompilationResponse { compilation_metadata });
responses.add(NodeGraphMessage::SendGraph);
}
}
@@ -321,31 +220,13 @@ impl NodeGraphExecutor {
Ok(())
}
fn debug_render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque<Message>) {
// Setup rendering
let mut render = SvgRender::new();
let render_params = RenderParams {
view_mode: ViewMode::Normal,
culling_bounds: None,
thumbnail: false,
hide_artboards: false,
for_export: false,
for_mask: false,
alignment_parent_transform: None,
};
// Render SVG
render_object.render_svg(&mut render, &render_params);
// Concatenate the defs and the SVG into one string
render.wrap_with_transform(transform, None);
let svg = render.svg.to_svg_string();
// Send to frontend
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
fn process_node_graph_output(
&mut self,
node_graph_output: TaggedValue,
introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
transform: DAffine2,
responses: &mut VecDeque<Message>,
) -> Result<(), String> {
let mut render_output_metadata = RenderMetadata::default();
match node_graph_output {
TaggedValue::RenderOutput(render_output) => {
@@ -370,149 +251,156 @@ impl NodeGraphExecutor {
render_output_metadata = render_output.metadata;
}
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::RasterData(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::RasterData(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
_ => {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
}
};
responses.add(Message::EndBuffer {
render_metadata: render_output_metadata,
});
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
responses.add(Message::ProcessQueue((render_output_metadata, introspected_inputs)));
Ok(())
}
}
// pub enum AnimationState {
// #[default]
// Stopped,
// Playing {
// start: f64,
// },
// Paused {
// start: f64,
// pause_time: f64,
// },
// }
// Re-export for usage by tests in other modules
#[cfg(test)]
pub use test::Instrumented;
// #[cfg(test)]
// pub use test::Instrumented;
#[cfg(test)]
mod test {
use std::sync::Arc;
// #[cfg(test)]
// mod test {
// use std::sync::Arc;
use super::*;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::test_utils::test_prelude::{self, NodeGraphLayer};
use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::NodeNetwork;
use graphene_std::Context;
use graphene_std::NodeInputDecleration;
use graphene_std::memo::IORecord;
use test_prelude::LayerNodeIdentifier;
// use super::*;
// use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
// use crate::test_utils::test_prelude::{self, NodeGraphLayer};
// use graph_craft::ProtoNodeIdentifier;
// use graph_craft::document::NodeNetwork;
// use graphene_std::Context;
// use graphene_std::NodeInputDecleration;
// use graphene_std::memo::IORecord;
// use test_prelude::LayerNodeIdentifier;
/// Stores all of the monitor nodes that have been attached to a graph
#[derive(Default)]
pub struct Instrumented {
protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,
protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
}
// /// Stores all of the monitor nodes that have been attached to a graph
// #[derive(Default)]
// pub struct Instrumented {
// protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,
// protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
// }
impl Instrumented {
/// Adds montior nodes to the network
fn add(&mut self, network: &mut NodeNetwork, path: &mut Vec<NodeId>) {
// Required to do seperately to satiate the borrow checker.
let mut monitor_nodes = Vec::new();
for (id, node) in network.nodes.iter_mut() {
// Recursively instrument
if let DocumentNodeImplementation::Network(nested) = &mut node.implementation {
path.push(*id);
self.add(nested, path);
path.pop();
}
let mut monitor_node_ids = Vec::with_capacity(node.inputs.len());
for input in &mut node.inputs {
let node_id = NodeId::new();
let old_input = std::mem::replace(input, NodeInput::node(node_id, 0));
monitor_nodes.push((old_input, node_id));
path.push(node_id);
monitor_node_ids.push(path.clone());
path.pop();
}
if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
path.push(*id);
self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());
self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
path.pop();
}
}
for (input, monitor_id) in monitor_nodes {
let monitor_node = DocumentNode {
inputs: vec![input],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)),
skip_deduplication: true,
..Default::default()
};
network.nodes.insert(monitor_id, monitor_node);
}
}
// impl Instrumented {
// /// Adds montior nodes to the network
// fn add(&mut self, network: &mut NodeNetwork, path: &mut Vec<NodeId>) {
// // Required to do seperately to satiate the borrow checker.
// let mut monitor_nodes = Vec::new();
// for (id, node) in network.nodes.iter_mut() {
// // Recursively instrument
// if let DocumentNodeImplementation::Network(nested) = &mut node.implementation {
// path.push(*id);
// self.add(nested, path);
// path.pop();
// }
// let mut monitor_node_ids = Vec::with_capacity(node.inputs.len());
// for input in &mut node.inputs {
// let node_id = NodeId::new();
// let old_input = std::mem::replace(input, NodeInput::node(node_id, 0));
// monitor_nodes.push((old_input, node_id));
// path.push(node_id);
// monitor_node_ids.push(path.clone());
// path.pop();
// }
// if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
// path.push(*id);
// self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());
// self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
// path.pop();
// }
// }
// for (input, monitor_id) in monitor_nodes {
// let monitor_node = DocumentNode {
// inputs: vec![input],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
// manual_composition: Some(graph_craft::generic!(T)),
// skip_deduplication: true,
// ..Default::default()
// };
// network.nodes.insert(monitor_id, monitor_node);
// }
// }
/// Instrument a graph and return a new [Instrumented] state.
pub fn new(network: &mut NodeNetwork) -> Self {
let mut instrumented = Self::default();
instrumented.add(network, &mut Vec::new());
instrumented
}
// /// Instrument a graph and return a new [Instrumented] state.
// pub fn new(network: &mut NodeNetwork) -> Self {
// let mut instrumented = Self::default();
// instrumented.add(network, &mut Vec::new());
// instrumented
// }
fn downcast<Input: NodeInputDecleration>(dynamic: Arc<dyn std::any::Any + Send + Sync>) -> Option<Input::Result>
where
Input::Result: Send + Sync + Clone + 'static,
{
// This is quite inflexible since it only allows the footprint as inputs.
if let Some(x) = dynamic.downcast_ref::<IORecord<(), Input::Result>>() {
Some(x.output.clone())
} else if let Some(x) = dynamic.downcast_ref::<IORecord<Footprint, Input::Result>>() {
Some(x.output.clone())
} else if let Some(x) = dynamic.downcast_ref::<IORecord<Context, Input::Result>>() {
Some(x.output.clone())
} else {
panic!("cannot downcast type for introspection");
}
}
// fn downcast<Input: NodeInputDecleration>(dynamic: Arc<dyn std::any::Any + Send + Sync>) -> Option<Input::Result>
// where
// Input::Result: Send + Sync + Clone + 'static,
// {
// // This is quite inflexible since it only allows the footprint as inputs.
// if let Some(x) = dynamic.downcast_ref::<IORecord<(), Input::Result>>() {
// Some(x.output.clone())
// } else if let Some(x) = dynamic.downcast_ref::<IORecord<Footprint, Input::Result>>() {
// Some(x.output.clone())
// } else if let Some(x) = dynamic.downcast_ref::<IORecord<Context, Input::Result>>() {
// Some(x.output.clone())
// } else {
// panic!("cannot downcast type for introspection");
// }
// }
/// Grab all of the values of the input every time it occurs in the graph.
pub fn grab_all_input<'a, Input: NodeInputDecleration + 'a>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator<Item = Input::Result> + 'a
where
Input::Result: Send + Sync + Clone + 'static,
{
self.protonodes_by_name
.get(&Input::identifier())
.map_or([].as_slice(), |x| x.as_slice())
.iter()
.filter_map(|inputs| inputs.get(Input::INDEX))
.filter_map(|input_monitor_node| runtime.executor.introspect(input_monitor_node).ok())
.filter_map(Instrumented::downcast::<Input>)
}
// /// Grab all of the values of the input every time it occurs in the graph.
// pub fn grab_all_input<'a, Input: NodeInputDecleration + 'a>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator<Item = Input::Result> + 'a
// where
// Input::Result: Send + Sync + Clone + 'static,
// {
// self.protonodes_by_name
// .get(&Input::identifier())
// .map_or([].as_slice(), |x| x.as_slice())
// .iter()
// .filter_map(|inputs| inputs.get(Input::INDEX))
// .filter_map(|input_monitor_node| runtime.executor.introspect(input_monitor_node).ok())
// .filter_map(Instrumented::downcast::<Input>)
// }
pub fn grab_protonode_input<Input: NodeInputDecleration>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Input::Result>
where
Input::Result: Send + Sync + Clone + 'static,
{
let input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?;
// pub fn grab_protonode_input<Input: NodeInputDecleration>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Input::Result>
// where
// Input::Result: Send + Sync + Clone + 'static,
// {
// let input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?;
let dynamic = runtime.executor.introspect(input_monitor_node).ok()?;
// let dynamic = runtime.executor.introspect(input_monitor_node).ok()?;
Self::downcast::<Input>(dynamic)
}
// Self::downcast::<Input>(dynamic)
// }
pub fn grab_input_from_layer<Input: NodeInputDecleration>(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, runtime: &NodeRuntime) -> Option<Input::Result>
where
Input::Result: Send + Sync + Clone + 'static,
{
let node_graph_layer = NodeGraphLayer::new(layer, network_interface);
let node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?;
self.grab_protonode_input::<Input>(&vec![node], runtime)
}
}
}
// pub fn grab_input_from_layer<Input: NodeInputDecleration>(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, runtime: &NodeRuntime) -> Option<Input::Result>
// where
// Input::Result: Send + Sync + Clone + 'static,
// {
// let node_graph_layer = NodeGraphLayer::new(layer, network_interface);
// let node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?;
// self.grab_protonode_input::<Input>(&vec![node], runtime)
// }
// }
// }

View File

@@ -14,6 +14,7 @@ use graphene_std::memo::IORecord;
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender};
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_std::text::FontCache;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId};
use graphene_std::vector::style::ViewMode;
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
@@ -24,8 +25,9 @@ use spin::Mutex;
use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender};
/// Persistent data between graph executions. It's updated via message passing from the editor thread with [`GraphRuntimeRequest`]`.
/// Some of these fields are put into a [`WasmEditorApi`] which is passed to the final compiled graph network upon each execution.
/// Persistent data between graph evaluations. It's updated via message passing from the editor thread with [`GraphRuntimeRequest`]`.
/// [`PortfolioMessage::CompileActiveDocument`] and [`PortfolioMessage::RenderActiveDocument`] are the two main entry points
/// Some of these fields are inserted into the network at compile time using the scope system
/// Once the implementation is finished, this will live in a separate thread. Right now it's part of the main JS thread, but its own separate JS stack frame independent from the editor.
pub struct NodeRuntime {
#[cfg(test)]
@@ -33,14 +35,11 @@ pub struct NodeRuntime {
#[cfg(not(test))]
executor: DynamicExecutor,
receiver: Receiver<GraphRuntimeRequest>,
sender: InternalNodeGraphUpdateSender,
editor_preferences: EditorPreferences,
old_graph: Option<NodeNetwork>,
update_thumbnails: bool,
sender: NodeGraphRuntimeSender,
application_io: Option<Arc<WasmApplicationIo>>,
editor_api: Arc<WasmEditorApi>,
node_graph_errors: GraphErrors,
monitor_nodes: Vec<Vec<NodeId>>,
/// Which node is inspected and which monitor node is used (if any) for the current execution
inspect_state: Option<InspectState>,
@@ -48,26 +47,24 @@ pub struct NodeRuntime {
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
// TODO: Remove, it doesn't need to be persisted anymore
/// The current renders of the thumbnails for layer nodes.
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
vector_modify: HashMap<NodeId, VectorData>,
/// Stored in order to check for changes before sending to the frontend.
thumbnail_render_tagged_values: HashMap<CompiledProtonodeInput, TaggedValue>,
}
/// Messages passed from the editor thread to the node runtime thread.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum GraphRuntimeRequest {
GraphUpdate(GraphUpdate),
ExecutionRequest(ExecutionRequest),
FontCacheUpdate(FontCache),
EditorPreferencesUpdate(EditorPreferences),
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct GraphUpdate {
pub(super) network: NodeNetwork,
/// The node that should be temporary inspected during execution
pub(super) inspect_node: Option<NodeId>,
CompilationRequest(CompilationRequest),
// Makes a request to evaluate the network and stores data for the list of output connectors
// Should only monitor data for nodes which need their thumbnails.
EvaluationRequest(EvaluationRequest),
// Renders thumbnails for the data from the last execution
// If the upstream node stores data for the context override, then another evaluation must be performed at the input
// This is performed separately from execution requests, since thumbnails for animation should be updated once every 50ms or so.
ThumbnailRenderRequest(HashSet<CompiledProtonodeInput>),
// Request the data from a list of node inputs. For example, used by vector modify to get the data at the input of every Path node.
// Can also be used by the spreadsheet/introspection system
IntrospectionRequest(HashSet<(CompiledProtonodeInput, IntrospectMode)>),
}
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -81,21 +78,14 @@ pub struct ExportConfig {
}
#[derive(Clone)]
struct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>);
struct NodeGraphRuntimeSender(Sender<NodeGraphUpdate>);
impl InternalNodeGraphUpdateSender {
fn send_generation_response(&self, response: CompilationResponse) {
self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send response")
impl NodeGraphRuntimeSender {
fn send_compilation_response(&self, response: CompilationResponse) {
self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send compilation response")
}
fn send_execution_response(&self, response: ExecutionResponse) {
self.0.send(NodeGraphUpdate::ExecutionResponse(response)).expect("Failed to send response")
}
}
impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender {
fn send(&self, message: NodeGraphUpdateMessage) {
self.0.send(NodeGraphUpdate::NodeGraphUpdateMessage(message)).expect("Failed to send response")
fn send_evaluation_response(&self, response: EvaluationResponse) {
self.0.send(NodeGraphUpdate::EvaluationResponse(response)).expect("Failed to send evaluation response")
}
}
@@ -106,151 +96,135 @@ impl NodeRuntime {
Self {
executor: DynamicExecutor::default(),
receiver,
sender: InternalNodeGraphUpdateSender(sender.clone()),
editor_preferences: EditorPreferences::default(),
old_graph: None,
update_thumbnails: true,
sender: NodeGraphRuntimeSender(sender.clone()),
editor_api: WasmEditorApi {
font_cache: FontCache::default(),
editor_preferences: Box::new(EditorPreferences::default()),
node_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)),
application_io: None,
}
.into(),
application_io: None,
node_graph_errors: Vec::new(),
monitor_nodes: Vec::new(),
substitutions: preprocessor::generate_node_substitutions(),
thumbnail_renders: Default::default(),
vector_modify: Default::default(),
thumbnail_render_tagged_values: HashSet::new(),
inspect_state: None,
}
}
pub async fn run(&mut self) {
if self.editor_api.application_io.is_none() {
self.editor_api = WasmEditorApi {
#[cfg(not(test))]
application_io: Some(WasmApplicationIo::new().await.into()),
#[cfg(test)]
application_io: Some(WasmApplicationIo::new_offscreen().await.into()),
font_cache: self.editor_api.font_cache.clone(),
node_graph_message_sender: Box::new(self.sender.clone()),
editor_preferences: Box::new(self.editor_preferences.clone()),
}
.into();
if self.application_io.is_none() {
#[cfg(not(test))]
self.application_io = Some(Arc::new(WasmApplicationIo::new().await));
#[cfg(test)]
self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await));
}
let mut font = None;
let mut preferences = None;
let mut graph = None;
let mut execution = None;
// TODO: This deduplication of messages will probably cause more issues than it solved
// let mut graph = None;
// let mut execution = None;
// let mut thumbnails = None;
// let mut introspection = None;
// for request in self.receiver.try_iter() {
// match request {
// GraphRuntimeRequest::CompilationRequest(_) => graph = Some(request),
// GraphRuntimeRequest::EvaluationRequest(_) => execution = Some(request),
// GraphRuntimeRequest::ThumbnailRenderResponse(_) => thumbnails = Some(request),
// GraphRuntimeRequest::IntrospectionResponse(_) => introspection = Some(request),
// }
// }
// let requests = [font, preferences, graph, execution].into_iter().flatten();
for request in self.receiver.try_iter() {
match request {
GraphRuntimeRequest::GraphUpdate(_) => graph = Some(request),
GraphRuntimeRequest::ExecutionRequest(_) => execution = Some(request),
GraphRuntimeRequest::FontCacheUpdate(_) => font = Some(request),
GraphRuntimeRequest::EditorPreferencesUpdate(_) => preferences = Some(request),
}
}
let requests = [font, preferences, graph, execution].into_iter().flatten();
for request in requests {
match request {
GraphRuntimeRequest::FontCacheUpdate(font_cache) => {
self.editor_api = WasmEditorApi {
font_cache,
application_io: self.editor_api.application_io.clone(),
node_graph_message_sender: Box::new(self.sender.clone()),
editor_preferences: Box::new(self.editor_preferences.clone()),
}
.into();
if let Some(graph) = self.old_graph.clone() {
// We ignore this result as compilation errors should have been reported in an earlier iteration
let _ = self.update_network(graph).await;
}
}
GraphRuntimeRequest::EditorPreferencesUpdate(preferences) => {
self.editor_preferences = preferences.clone();
self.editor_api = WasmEditorApi {
font_cache: self.editor_api.font_cache.clone(),
application_io: self.editor_api.application_io.clone(),
node_graph_message_sender: Box::new(self.sender.clone()),
editor_preferences: Box::new(preferences),
}
.into();
if let Some(graph) = self.old_graph.clone() {
// We ignore this result as compilation errors should have been reported in an earlier iteration
let _ = self.update_network(graph).await;
}
}
GraphRuntimeRequest::GraphUpdate(GraphUpdate { mut network, inspect_node }) => {
GraphRuntimeRequest::CompilationRequest(CompilationRequest {
mut network,
font_cache,
editor_metadata,
}) => {
// Insert the monitor node to manage the inspection
self.inspect_state = inspect_node.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect));
// self.inspect_state = inspect_node.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect));
self.old_graph = Some(network.clone());
self.node_graph_errors.clear();
let result = self.update_network(network).await;
self.update_thumbnails = true;
self.sender.send_generation_response(CompilationResponse {
self.sender.send_compilation_response(CompilationResponse {
result,
node_graph_errors: self.node_graph_errors.clone(),
});
}
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
let transform = render_config.viewport.transform;
GraphRuntimeRequest::EvaluationRequest(EvaluationRequest {
evaluation_id,
context,
inputs_to_monitor,
// custom_node_to_evaluate
}) => {
for (protonode_input, introspect_mode) in inputs_to_monitor {
self.executor.set_introspect(protonode_input, introspect_mode)
}
let transform = context.render_config.viewport.transform;
let result = self.execute_network(render_config).await;
let mut responses = VecDeque::new();
// TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes
self.process_monitor_nodes(&mut responses, self.update_thumbnails);
self.update_thumbnails = false;
// Resolve the result from the inspection by accessing the monitor node
let inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor));
let introspected_inputs = Vec::new();
for (protonode_input, mode) in inputs_to_introspect {
let Ok(introspected_data) = self.executor.introspect(protonode_input, mode) else {
log::error!("Could not introspect node from input: {:?}", protonode_input);
continue;
};
introspected_inputs.push((protonode_input, mode, introspected_data));
}
self.sender.send_execution_response(ExecutionResponse {
execution_id,
self.sender.send_evaluation_response(EvaluationResponse {
evaluation_id,
result,
responses,
transform,
vector_modify: self.vector_modify.clone(),
inspect_result,
introspected_inputs,
});
}
GraphRuntimeRequest::ThumbnailRenderRequest(input_to_render) => {
let mut thumbnail_response = ThumbnailRenderResponse::default();
for input in input_to_render {}
self.sender.send_thumbnail_render_response(thumbnail_response);
}
GraphRuntimeRequest::IntrospectionRequest(inputs_to_introspect) => {
self.sender.send_introspection_response(introspection_response);
}
}
}
}
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<CompilationMetadata, String> {
preprocessor::expand_network(&mut graph, &self.substitutions);
// Creates a network where the node paths to the document network are prefixed with NodeId(0)
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
// We assume only one output
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {};
let proto_network = match c.compile_single(scoped_network) {
// Modifies the NodeNetwork so the tagged values are removed and the document nodes with protonode implementations have their protonode ids set
// Needs to return a mapping of absolute input connectors to protonode callers, types for protonodes, and callers for protonodes, add/remove delta for resolved types
let (proto_network, protonode_callers_for_value, protonode_callers_for_node) = match scoped_network.flatten() {
Ok(network) => network,
Err(e) => return Err(e),
Err(e) => {
log::error!("Error compiling network: {e:?}");
return;
}
};
self.monitor_nodes = proto_network
.nodes
.iter()
.filter(|(_, node)| node.identifier == "graphene_core::memo::MonitorNode".into())
.map(|(_, node)| node.original_location.path.clone().unwrap_or_default())
.collect::<Vec<_>>();
assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?");
self.executor.update(proto_network).await.map_err(|e| {
self.node_graph_errors.clone_from(&e);
format!("{e:?}")
})
assert_ne!(proto_network.len(), 0, "No proto nodes exist?");
let result = match self.executor.update(proto_network).await {
Ok((types_to_add, types_to_remove)) => {
// Used to remove thumbnails from the mapping of SNI to rendered SVG strings on the frontend, which occurs when the SNI is removed
// When native frontend rendering is possible, the strings can just be stored in the network interface for each protonode with the rest of the type metadata
Ok(CompilationMetadata {
protonode_callers_for_value,
protonode_callers_for_node,
types_to_add,
types_to_remove,
})
}
Err(e) => {
self.node_graph_errors.clone_from(&e);
Err(format!("{e:?}"))
}
};
}
async fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {
@@ -269,117 +243,6 @@ impl NodeRuntime {
Ok(result)
}
/// Updates state data
pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque<FrontendMessage>, update_thumbnails: bool) {
// TODO: Consider optimizing this since it's currently O(m*n^2), with a sort it could be made O(m * n*log(n))
self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id)));
for monitor_node_path in &self.monitor_nodes {
// Skip the inspect monitor node
if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) {
continue;
}
// The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID
let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else {
warn!("Monitor node has invalid node id");
continue;
};
// Extract the monitor node's stored `GraphicElement` data.
let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else {
// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)
#[cfg(debug_assertions)]
warn!("Failed to introspect monitor node {}", self.executor.introspect(monitor_node_path).unwrap_err());
continue;
};
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::GraphicElement>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::Artboard>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
// Insert the vector modify if we are dealing with vector data
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
let default = Instance::default();
self.vector_modify.insert(
parent_network_node_id,
record.output.instance_ref_iter().next().unwrap_or_else(|| default.to_instance_ref()).instance.clone(),
);
} else {
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
}
}
}
// If this is `GraphicElement` data:
// Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
fn process_graphic_element(
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
parent_network_node_id: NodeId,
graphic_element: &impl GraphicElementRendered,
responses: &mut VecDeque<FrontendMessage>,
update_thumbnails: bool,
) {
// RENDER THUMBNAIL
if !update_thumbnails {
return;
}
// Skip thumbnails if the layer is too complex (for performance)
if graphic_element.render_complexity() > 1000 {
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
if old.is_none_or(|v| !v.is_empty()) {
responses.push_back(FrontendMessage::UpdateNodeThumbnail {
id: parent_network_node_id,
value: "<svg viewBox=\"0 0 10 10\"><title>Dense thumbnail omitted for performance</title><line x1=\"0\" y1=\"10\" x2=\"10\" y2=\"0\" stroke=\"red\" /></svg>".to_string(),
});
}
return;
}
let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true);
// Render the thumbnail from a `GraphicElement` into an SVG string
let render_params = RenderParams {
view_mode: ViewMode::Normal,
culling_bounds: bounds,
thumbnail: true,
hide_artboards: false,
for_export: false,
for_mask: false,
alignment_parent_transform: None,
};
let mut render = SvgRender::new();
graphic_element.render_svg(&mut render, &render_params);
// And give the SVG a viewbox and outer <svg>...</svg> wrapper tag
let [min, max] = bounds.unwrap_or_default();
render.format_svg(min, max);
// UPDATE FRONTEND THUMBNAIL
let new_thumbnail_svg = render.svg;
let old_thumbnail_svg = thumbnail_renders.entry(parent_network_node_id).or_default();
if old_thumbnail_svg != &new_thumbnail_svg {
responses.push_back(FrontendMessage::UpdateNodeThumbnail {
id: parent_network_node_id,
value: new_thumbnail_svg.to_svg_string(),
});
*old_thumbnail_svg = new_thumbnail_svg;
}
}
}
pub async fn introspect_node(path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
let runtime = NODE_RUNTIME.lock();
if let Some(ref mut runtime) = runtime.as_ref() {
return runtime.executor.introspect(path);
}
Err(IntrospectError::RuntimeNotReady)
}
pub async fn run_node_graph() -> bool {
@@ -394,81 +257,3 @@ pub async fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {
let mut node_runtime = NODE_RUNTIME.lock();
node_runtime.replace(runtime)
}
/// Which node is inspected and which monitor node is used (if any) for the current execution
#[derive(Debug, Clone, Copy)]
struct InspectState {
inspect_node: NodeId,
monitor_node: NodeId,
}
/// The resulting value from the temporary inspected during execution
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub struct InspectResult {
#[cfg(not(feature = "decouple-execution"))]
introspected_data: Option<Arc<dyn std::any::Any + Send + Sync + 'static>>,
#[cfg(feature = "decouple-execution")]
introspected_data: Option<TaggedValue>,
pub inspect_node: NodeId,
}
impl InspectResult {
pub fn take_data(&mut self) -> Option<Arc<dyn std::any::Any + Send + Sync + 'static>> {
#[cfg(not(feature = "decouple-execution"))]
return self.introspected_data.clone();
#[cfg(feature = "decouple-execution")]
return self.introspected_data.take().map(|value| value.to_any());
}
}
// This is very ugly but is required to be inside a message
impl PartialEq for InspectResult {
fn eq(&self, other: &Self) -> bool {
self.inspect_node == other.inspect_node
}
}
impl InspectState {
/// Insert the monitor node to manage the inspection
pub fn monitor_inspect_node(network: &mut NodeNetwork, inspect_node: NodeId) -> Self {
let monitor_id = NodeId::new();
// It is necessary to replace the inputs before inserting the monitor node to avoid changing the input of the new monitor node
for input in network.nodes.values_mut().flat_map(|node| node.inputs.iter_mut()).chain(&mut network.exports) {
let NodeInput::Node { node_id, output_index, .. } = input else { continue };
// We only care about the primary output of our inspect node
if *output_index != 0 || *node_id != inspect_node {
continue;
}
*node_id = monitor_id;
}
let monitor_node = DocumentNode {
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)),
skip_deduplication: true,
..Default::default()
};
network.nodes.insert(monitor_id, monitor_node);
Self {
inspect_node,
monitor_node: monitor_id,
}
}
/// Resolve the result from the inspection by accessing the monitor node
fn access(&self, executor: &DynamicExecutor) -> Option<InspectResult> {
let introspected_data = executor.introspect(&[self.monitor_node]).inspect_err(|e| warn!("Failed to introspect monitor node {e}")).ok();
// TODO: Consider displaying the error instead of ignoring it
#[cfg(feature = "decouple-execution")]
let introspected_data = introspected_data.as_ref().and_then(|data| TaggedValue::try_from_std_any_ref(data).ok());
Some(InspectResult {
inspect_node: self.inspect_node,
introspected_data,
})
}
}