merge onto master

This commit is contained in:
Adam
2025-07-16 01:40:49 -07:00
parent 99966d848d
commit 1398405529
60 changed files with 2861 additions and 3229 deletions
+41 -6
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, ());
}
@@ -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);
}
@@ -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,
@@ -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);
}
@@ -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())
@@ -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,
@@ -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)]
+19 -3
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]
@@ -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)]
@@ -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);
}
}
@@ -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);
}
@@ -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);
}
}
}
@@ -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,
@@ -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,
@@ -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);
@@ -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);
@@ -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)]
@@ -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)]
@@ -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,
}
@@ -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),
}
@@ -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,
}
@@ -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
}
@@ -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)]
@@ -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;
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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) {
@@ -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
}
@@ -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);
@@ -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);
@@ -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;
@@ -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 => {
@@ -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
}
@@ -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 {
@@ -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 {
+263 -375
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)
// }
// }
// }
+111 -326
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,
})
}
}
+4 -4
View File
@@ -124,10 +124,10 @@ export class SendUIMetadata extends JsMessage {
readonly nodeTypes!: FrontendNodeType[];
}
export class UpdateNodeThumbnail extends JsMessage {
readonly id!: bigint;
export class UpdateThumbnails extends JsMessage {
readonly add!: [bigint, string][];
readonly value!: string;
readonly clear!: bigint[];
}
export class UpdateNodeGraphSelection extends JsMessage {
@@ -1683,7 +1683,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateNodeGraphTransform,
UpdateNodeGraphControlBarLayout,
UpdateNodeGraphSelection,
UpdateNodeThumbnail,
UpdateThumbnails: UpdateThumbnail,
UpdateOpenDocumentsList,
UpdatePropertyPanelSectionsLayout,
UpdateSpreadsheetLayout,
+8 -3
View File
@@ -24,7 +24,7 @@ import {
UpdateNodeGraphWires,
UpdateNodeGraphSelection,
UpdateNodeGraphTransform,
UpdateNodeThumbnail,
UpdateThumbnails,
UpdateWirePathInProgress,
} from "@graphite/messages";
@@ -168,9 +168,14 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeThumbnail, (updateNodeThumbnail) => {
editor.subscriptions.subscribeJsMessage(UpdateThumbnails, (updateThumbnail) => {
update((state) => {
state.thumbnails.set(updateNodeThumbnail.id, updateNodeThumbnail.value);
for (const [id, value] of updateThumbnail.add) {
state.thumbnails.set(id, value);
}
for (const id of updateThumbnail.clear) {
state.thumbnails.set(id, "");
}
return state;
});
});
+1
View File
@@ -234,6 +234,7 @@ impl CloneVarArgs for Arc<OwnedContextImpl> {
}
}
// Lifetime isnt necessary?
pub type Context<'a> = Option<Arc<OwnedContextImpl>>;
type DynRef<'a> = &'a (dyn Any + Send + Sync);
type DynBox = Box<dyn Any + Send + Sync>;
+11 -3
View File
@@ -37,6 +37,7 @@ pub use context::*;
pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
pub use memo::IntrospectMode;
pub use memo::MemoHash;
pub use num_traits;
pub use raster::Color;
@@ -58,11 +59,18 @@ pub trait Node<'i, Input> {
fn node_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
/// Serialize the node which is used for the `introspect` function which can retrieve values from monitor nodes.
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
log::warn!("Node::serialize not implemented for {}", std::any::type_name::<Self>());
/// Get the call argument or output data for the monitor node on the next evaluation after set_introspect_input
/// Also returns a boolean of whether the node was evaluated
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<Box<dyn std::any::Any + Send + Sync>> {
log::warn!("Node::introspect not implemented for {}", std::any::type_name::<Self>());
None
}
// The introspect mode is set before the graph evaluation, and tells the monitor node what data to store
fn set_introspect(&self, _introspect_mode: IntrospectMode) {
log::warn!("Node::set_introspect not implemented for {}", std::any::type_name::<Self>());
}
}
mod types;
+47 -21
View File
@@ -107,47 +107,73 @@ pub mod impure_memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode");
}
/// Stores both what a node was called with and what it returned.
#[derive(Clone, Debug)]
pub struct IORecord<I, O> {
pub input: I,
pub output: O,
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum IntrospectMode {
Input,
Data,
}
/// Caches the output of the last graph evaluation for introspection
#[derive(Default)]
pub struct MonitorNode<I, T, N> {
pub struct MonitorNode<I, O, N> {
#[allow(clippy::type_complexity)]
io: Arc<Mutex<Option<Arc<IORecord<I, T>>>>>,
input: Arc<Mutex<Option<Box<I>>>>,
output: Arc<Mutex<Option<Box<O>>>>,
// Gets set to true by the editor when before evaluating the network, then reset when the monitor node is evaluated
introspect_input: Arc<Mutex<bool>>,
introspect_output: Arc<Mutex<bool>>,
node: N,
}
impl<'i, T, I, N> Node<'i, I> for MonitorNode<I, T, N>
impl<'i, I, O, N> Node<'i, I> for MonitorNode<I, O, N>
where
I: Clone + 'static + Send + Sync,
T: Clone + 'static + Send + Sync,
for<'a> N: Node<'a, I, Output: Future<Output = T> + WasmNotSend> + 'i,
O: Clone + 'static + Send + Sync,
for<'a> N: Node<'a, I, Output: Future<Output = O> + WasmNotSend> + Send + Sync + 'i,
{
type Output = DynFuture<'i, T>;
type Output = DynFuture<'i, O>;
fn eval(&'i self, input: I) -> Self::Output {
let io = self.io.clone();
let output_fut = self.node.eval(input.clone());
Box::pin(async move {
let output = output_fut.await;
*io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() }));
let output = self.node.eval(input.clone()).await;
let mut introspect_input = self.introspect_input.lock().unwrap();
if *introspect_input {
*self.input.lock().unwrap() = Some(Box::new(input));
*introspect_input = false;
}
let mut introspect_output = self.introspect_output.lock().unwrap();
if *introspect_output {
*self.output.lock().unwrap() = Some(Box::new(output.clone()));
*introspect_output = false;
}
output
})
}
fn serialize(&self) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
let io = self.io.lock().unwrap();
(io).as_ref().map(|output| output.clone() as Arc<dyn std::any::Any + Send + Sync>)
// After introspecting, the input/output get set to None because the Arc is moved to the editor where it can be directly accessed.
fn introspect(&self, introspect_mode: IntrospectMode) -> Option<Box<dyn std::any::Any + Send + Sync>> {
match introspect_mode {
IntrospectMode::Input => self.input.lock().unwrap().take().map(|input| input as Box<dyn std::any::Any + Send + Sync>),
IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Box<dyn std::any::Any + Send + Sync>),
}
}
fn set_introspect(&self, introspect_mode: IntrospectMode) {
match introspect_mode {
IntrospectMode::Input => *self.introspect_input.lock().unwrap() = true,
IntrospectMode::Data => *self.introspect_output.lock().unwrap() = true,
}
}
}
impl<I, T, N> MonitorNode<I, T, N> {
pub fn new(node: N) -> MonitorNode<I, T, N> {
MonitorNode { io: Arc::new(Mutex::new(None)), node }
impl<I, O, N> MonitorNode<I, O, N> {
pub fn new(node: N) -> MonitorNode<I, O, N> {
MonitorNode {
input: Arc::new(Mutex::new(None)),
output: Arc::new(Mutex::new(None)),
introspect_input: Arc::new(Mutex::new(false)),
introspect_output: Arc::new(Mutex::new(false)),
node,
}
}
}
-4
View File
@@ -24,10 +24,6 @@ where
fn reset(&self) {
self.0.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.0.serialize()
}
}
impl<'i, N: for<'a> Node<'a, I>, I: 'i> TypeNode<N, I, <N as Node<'i, I>>::Output> {
pub fn new(node: N) -> Self {
+8 -15
View File
@@ -132,6 +132,7 @@ pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
pub type SharedNodeContainer = std::sync::Arc<NodeContainer>;
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
pub type MonitorConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>;
#[derive(Clone)]
pub struct NodeContainer {
@@ -208,11 +209,10 @@ where
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
{
let node_name = self.node.node_name();
let input = Box::new(input);
let future = self.node.eval(input);
Box::pin(async move {
let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode Input {e} in: \n{node_name}"));
let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode Input {e} in: \n{:?}", self.node.node_name()));
*out
})
}
@@ -220,11 +220,8 @@ where
fn reset(&self) {
self.node.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.node.serialize()
}
}
impl<I, O> DowncastBothNode<I, O> {
pub const fn new(node: SharedNodeContainer) -> Self {
Self {
@@ -234,6 +231,11 @@ impl<I, O> DowncastBothNode<I, O> {
}
}
}
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
DowncastBothNode::new(n)
}
pub struct FutureWrapperNode<Node> {
node: Node,
}
@@ -252,11 +254,6 @@ where
fn reset(&self) {
self.node.reset();
}
#[inline(always)]
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.node.serialize()
}
}
impl<N> FutureWrapperNode<N> {
@@ -294,10 +291,6 @@ where
fn reset(&self) {
self.node.reset();
}
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.node.serialize()
}
}
impl<'input, I, O, N> DynAnyNode<I, O, N>
where
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::Node;
use crate::registry::Node;
use std::marker::PhantomData;
/// This is how we can generically define composition of two nodes.
+9
View File
@@ -84,3 +84,12 @@ impl std::fmt::Display for NodeId {
write!(f, "{}", self.0)
}
}
// Stable Node Id of a protonode, generated during compilation based on the input values
pub type SNI = NodeId;
// An input of a compiled protonode, used to reference thumbnails, which are stored on a per input basis
pub type CompiledProtonodeInput = (NodeId, usize);
// Path to the protonode in the document network
pub type ProtonodePath = Box<[NodeId]>;
File diff suppressed because it is too large Load Diff
@@ -108,6 +108,16 @@ macro_rules! tagged_value {
_ => Err(format!("Cannot convert {:?} to TaggedValue",std::any::type_name_of_val(input))),
}
}
// Check for equality between a dynamic type and a tagged value without cloning
pub fn compare_value_to_dyn_any(&self, any: Box<dyn std::any::Any + Send + Sync>) -> bool {
match self {
TaggedValue::None => any.downcast_ref::<()>().is_some(),
$(TaggedValue::$identifier(value) => {any.downcast_ref::<$ty>().map_or(false, |v| v==value)}, )*
TaggedValue::RenderOutput(value) => any.downcast_ref::<RenderOutput>().map_or(false, |v| v==value),
TaggedValue::SurfaceFrame(value) => any.downcast_ref::<SurfaceFrame>().map_or(false, |v| v==value),
TaggedValue::EditorApi(value) => any.downcast_ref::<Arc<WasmEditorApi>>().map_or(false, |v| v==value),
}
}
pub fn from_type(input: &Type) -> Option<Self> {
match input {
Type::Generic(_) => None,
@@ -372,6 +382,18 @@ impl TaggedValue {
_ => panic!("Passed value is not of type u32"),
}
}
pub fn as_renderable<'a>(value: &'a TaggedValue) -> Option<&'a dyn graphene_svg_renderer::GraphicElementRendered> {
match value {
TaggedValue::VectorData(v) => Some(v),
TaggedValue::RasterData(r) => Some(r),
TaggedValue::GraphicElement(e) => Some(e),
TaggedValue::GraphicGroup(g) => Some(g),
TaggedValue::ArtboardGroup(a) => Some(a),
TaggedValue::Artboard(a) => Some(a),
_ => None,
}
}
}
impl Display for TaggedValue {
@@ -1,36 +1 @@
use crate::document::NodeNetwork;
use crate::proto::{LocalFuture, ProtoNetwork};
use std::error::Error;
pub struct Compiler {}
impl Compiler {
pub fn compile(&self, mut network: NodeNetwork) -> impl Iterator<Item = Result<ProtoNetwork, String>> {
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
network.populate_dependants();
for id in node_ids {
network.flatten(id);
}
network.resolve_scope_inputs();
network.remove_redundant_id_nodes();
// network.remove_dead_nodes(0);
let proto_networks = network.into_proto_networks();
proto_networks.map(move |mut proto_network| {
proto_network.resolve_inputs()?;
proto_network.generate_stable_node_ids();
Ok(proto_network)
})
}
pub fn compile_single(&self, network: NodeNetwork) -> Result<ProtoNetwork, String> {
assert_eq!(network.exports.len(), 1, "Graph with multiple outputs not yet handled");
let Some(proto_network) = self.compile(network).next() else {
return Err("Failed to convert graph into proto graph".to_string());
};
proto_network
}
}
pub trait Executor<I, O> {
fn execute(&self, input: I) -> LocalFuture<'_, Result<O, Box<dyn Error>>>;
}
File diff suppressed because it is too large Load Diff
-6
View File
@@ -1,6 +1,5 @@
use crate::document::NodeNetwork;
use crate::graphene_compiler::Compiler;
use crate::proto::ProtoNetwork;
pub fn load_network(document_string: &str) -> NodeNetwork {
let document: serde_json::Value = serde_json::from_str(document_string).expect("Failed to parse document");
@@ -8,11 +7,6 @@ pub fn load_network(document_string: &str) -> NodeNetwork {
serde_json::from_str::<NodeNetwork>(&document).expect("Failed to parse document")
}
pub fn compile(network: NodeNetwork) -> ProtoNetwork {
let compiler = Compiler {};
compiler.compile_single(network).unwrap()
}
pub fn load_from_name(name: &str) -> NodeNetwork {
let content = std::fs::read(format!("../../demo-artwork/{name}.graphite")).expect("failed to read file");
let content = std::str::from_utf8(&content).unwrap();
+4 -4
View File
@@ -3,7 +3,7 @@ use fern::colors::{Color, ColoredLevelConfig};
use futures::executor::block_on;
use graph_craft::document::*;
use graph_craft::graphene_compiler::{Compiler, Executor};
use graph_craft::proto::ProtoNetwork;
use graph_craft::proto::{ProtoNetwork, ProtoNode};
use graph_craft::util::load_network;
use graph_craft::wasm_application_io::EditorPreferences;
use graphene_core::text::FontCache;
@@ -180,17 +180,17 @@ fn fix_nodes(network: &mut NodeNetwork) {
}
}
}
fn compile_graph(document_string: String, editor_api: Arc<WasmEditorApi>) -> Result<ProtoNetwork, Box<dyn Error>> {
fn compile_graph(document_string: String, editor_api: Arc<WasmEditorApi>) -> Result<Vec<ProtoNode>, Box<dyn Error>> {
let mut network = load_network(&document_string);
fix_nodes(&mut network);
let substitutions = preprocessor::generate_node_substitutions();
preprocessor::expand_network(&mut network, &substitutions);
let wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
let mut wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
let compiler = Compiler {};
compiler.compile_single(wrapped_network).map_err(|x| x.into())
wrapped_network.flatten().map(|result|result.0).map_err(|x| x.into())
}
fn create_executor(proto_network: ProtoNetwork) -> Result<DynamicExecutor, Box<dyn Error>> {
@@ -2,13 +2,13 @@ use criterion::BenchmarkGroup;
use criterion::measurement::Measurement;
use futures::executor::block_on;
use graph_craft::proto::ProtoNetwork;
use graph_craft::util::{DEMO_ART, compile, load_from_name};
use graph_craft::util::{DEMO_ART, load_from_name};
use interpreted_executor::dynamic_executor::DynamicExecutor;
pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {
let network = load_from_name(name);
let proto_network = compile(network);
let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap();
let mut network = load_from_name(name);
let proto_network = network.flatten().unwrap();
let executor = block_on(DynamicExecutor::new(proto_network.0)).unwrap();
(executor, proto_network)
}
@@ -7,8 +7,8 @@ use graphene_std::transform::Footprint;
use interpreted_executor::dynamic_executor::DynamicExecutor;
fn update_executor<M: Measurement>(name: &str, c: &mut BenchmarkGroup<M>) {
let network = load_from_name(name);
let proto_network = compile(network);
let mut network = load_from_name(name);
let proto_network = network.flatten().unwrap().0;
let empty = ProtoNetwork::default();
let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap();
@@ -30,8 +30,8 @@ fn update_executor_demo(c: &mut Criterion) {
}
fn run_once<M: Measurement>(name: &str, c: &mut BenchmarkGroup<M>) {
let network = load_from_name(name);
let proto_network = compile(network);
let mut network = load_from_name(name);
let proto_network = network.flatten().unwrap().0;
let executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap();
let footprint = Footprint::default();
@@ -1,11 +1,15 @@
use crate::node_registry;
use crate::node_registry::{MONITOR_NODES, NODE_REGISTRY};
use dyn_any::StaticType;
use graph_craft::Type;
use graph_craft::document::NodeId;
use glam::DAffine2;
use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode};
use graph_craft::graphene_compiler::Executor;
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext};
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, downcast_node};
use graph_craft::proto::{GraphErrorType, GraphErrors};
use graph_craft::{Type, concrete};
use graphene_std::application_io::{ExportFormat, RenderConfig, TimingInformation};
use graphene_std::memo::{IntrospectMode, MonitorNode};
use graphene_std::transform::Footprint;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
use graphene_std::{NodeIOTypes, OwnedContextImpl};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::sync::Arc;
@@ -13,118 +17,116 @@ use std::sync::Arc;
/// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.
#[derive(Clone)]
pub struct DynamicExecutor {
output: NodeId,
output: Option<SNI>,
/// Stores all of the dynamic node structs.
tree: BorrowTree,
/// Stores the types of the proto nodes.
typing_context: TypingContext,
// This allows us to keep the nodes around for one more frame which is used for introspection
orphaned_nodes: HashSet<NodeId>,
// TODO: Add lifetime for removed nodes so that if a SNI changes, then changes back to its previous SNI, the node does
// not have to be reinserted
// lifetime: HashSet<(SNI, usize)>,
}
impl Default for DynamicExecutor {
fn default() -> Self {
Self {
output: Default::default(),
output: None,
tree: Default::default(),
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
orphaned_nodes: HashSet::new(),
typing_context: TypingContext::new(&NODE_REGISTRY, &MONITOR_NODES),
}
}
}
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct NodeTypes {
pub inputs: Vec<Type>,
pub output: Type,
}
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ResolvedDocumentNodeTypes {
pub types: HashMap<Vec<NodeId>, NodeTypes>,
}
type Path = Box<[NodeId]>;
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ResolvedDocumentNodeTypesDelta {
pub add: Vec<(Path, NodeTypes)>,
pub remove: Vec<Path>,
}
impl DynamicExecutor {
pub async fn new(proto_network: ProtoNetwork) -> Result<Self, GraphErrors> {
let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);
pub async fn new(proto_network: Vec<ProtoNode>) -> Result<Self, GraphErrors> {
let mut typing_context = TypingContext::default();
typing_context.update(&proto_network)?;
let output = proto_network.output;
let output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
let tree = BorrowTree::new(proto_network, &typing_context).await?;
Ok(Self {
tree,
output,
typing_context,
orphaned_nodes: HashSet::new(),
})
Ok(Self { tree, output, typing_context })
}
/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.
#[cfg_attr(debug_assertions, inline(never))]
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<ResolvedDocumentNodeTypesDelta, GraphErrors> {
self.output = proto_network.output;
pub async fn update(mut self, proto_network: Vec<ProtoNode>) -> Result<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), GraphErrors> {
self.output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
self.typing_context.update(&proto_network)?;
let (add, orphaned) = self.tree.update(proto_network, &self.typing_context).await?;
let old_to_remove = core::mem::replace(&mut self.orphaned_nodes, orphaned);
let mut remove = Vec::with_capacity(old_to_remove.len() - self.orphaned_nodes.len().min(old_to_remove.len()));
for node_id in old_to_remove {
if self.orphaned_nodes.contains(&node_id) {
let path = self.tree.free_node(node_id);
self.typing_context.remove_inference(node_id);
if let Some(path) = path {
remove.push(path);
}
}
// A protonode id can change while having the same document path, and the path can change while having the same stable node id.
// Either way, the mapping of paths to ids and ids to paths has to be kept in sync.
// The mapping of monitor node paths has to kept in sync as well.
let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?;
let mut remove = Vec::new();
for sni in orphaned_proto_nodes {
let Some(types) = self.typing_context.type_of(sni) else {
log::error!("Could not get type for protonode {sni} when removing");
continue;
};
remove.push((sni, types.inputs.len()));
self.tree.free_node(&sni, types.inputs.len());
self.typing_context.remove_inference(&sni);
}
let add = self.document_node_types(add.into_iter()).collect();
Ok(ResolvedDocumentNodeTypesDelta { add, remove })
let add_with_types = add
.into_iter()
.filter_map(|sni| {
let Some(types) = self.typing_context.type_of(sni) else {
log::debug!("Could not get type for added node: {sni}");
return None;
};
Some((sni, types.inputs.clone()))
})
.collect();
Ok((add_with_types, remove))
}
/// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path.
pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
self.tree.introspect(node_path)
/// Intospect the value for that specific protonode input, returning for example the cached value for a monitor node.
pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result<Box<dyn std::any::Any + Send + Sync>, IntrospectError> {
let node = self.get_monitor_node_container(protonode_input)?;
node.introspect(introspect_mode).ok_or(IntrospectError::IntrospectNotImplemented)
}
pub fn set_introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) {
let Ok(node) = self.get_monitor_node_container(protonode_input) else {
log::error!("Could not get monitor node for input: {:?}", protonode_input);
return;
};
node.set_introspect(introspect_mode);
}
pub fn get_monitor_node_container(&self, protonode_input: CompiledProtonodeInput) -> Result<SharedNodeContainer, IntrospectError> {
// The SNI of the monitor nodes are the ids of the protonode + input index
let monitor_node_id = NodeId(protonode_input.0.0 + protonode_input.1 as u64 + 1);
let inserted_node = self.tree.nodes.get(&monitor_node_id).ok_or(IntrospectError::ProtoNodeNotFound(monitor_node_id))?;
Ok(inserted_node.clone())
}
pub fn input_type(&self) -> Option<Type> {
self.typing_context.type_of(self.output).map(|node_io| node_io.call_argument.clone())
self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.call_argument.clone()))
}
pub fn tree(&self) -> &BorrowTree {
&self.tree
}
pub fn output(&self) -> NodeId {
pub fn output(&self) -> Option<SNI> {
self.output
}
pub fn output_type(&self) -> Option<Type> {
self.typing_context.type_of(self.output).map(|node_io| node_io.return_value.clone())
self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.return_value.clone()))
}
pub fn document_node_types<'a>(&'a self, nodes: impl Iterator<Item = Path> + 'a) -> impl Iterator<Item = (Path, NodeTypes)> + 'a {
nodes.flat_map(|id| self.tree.source_map().get(&id).map(|(_, b)| (id, b.clone())))
// TODO: https://github.com/GraphiteEditor/Graphite/issues/1767
// TODO: Non exposed inputs are not added to the inputs_source_map, so they are not included in the resolved_document_node_types. The type is still available in the typing_context. This only affects the UI-only "Import" node.
}
}
impl<I> Executor<I, TaggedValue> for &DynamicExecutor
where
I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
{
fn execute(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>> {
pub fn execute<I>(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>>
where
I: dyn_any::StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
{
Box::pin(async move {
use futures::FutureExt;
let output_node = self.output.ok_or("Could not execute network before compilation")?;
let result = self.tree.eval_tagged_value(self.output, input);
let result = self.tree.eval_tagged_value(output_node, input);
let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await;
match wrapped_result {
@@ -136,15 +138,98 @@ where
}
})
}
// If node to evaluate is None then the most downstream node is used
// pub async fn evaluate_from_node(&self, editor_context: EditorContext, node_to_evaluate: Option<SNI>) -> Result<TaggedValue, String> {
// let node_to_evaluate: NodeId = node_to_evaluate
// .or_else(|| self.output)
// .ok_or("Could not find output node when evaluating network. Has the network been compiled?")?;
// let input_type = self
// .typing_context
// .type_of(node_to_evaluate)
// .map(|node_io| node_io.call_argument.clone())
// .ok_or("Could not get input type of network to execute".to_string())?;
// let result = match input_type {
// t if t == concrete!(EditorContext) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()),
// t if t == concrete!(()) => (&self).execute((), node_to_evaluate).await.map_err(|e| e.to_string()),
// t => Err(format!("Invalid input type {t:?}")),
// };
// let result = match result {
// Ok(value) => value,
// Err(e) => return Err(e),
// };
// Ok(result)
// }
}
pub struct InputMapping {}
#[derive(Debug, Clone, Default)]
pub struct EditorContext {
// pub footprint: Option<Footprint>,
// pub downstream_transform: Option<DAffine2>,
// pub real_time: Option<f64>,
// pub animation_time: Option<f64>,
// pub index: Option<usize>,
// pub editor_var_args: Option<(Vec<String>, Vec<Arc<Box<[dyn std::any::Any + 'static + std::panic::UnwindSafe]>>>)>,
// TODO: Temporarily used to execute with RenderConfig as call argument, will be removed once these fields can be passed
// As a scope input to the reworked render node. This will allow the Editor Context to be used to evaluate any node
pub render_config: RenderConfig,
}
unsafe impl StaticType for EditorContext {
type Static = EditorContext;
}
// impl Default for EditorContext {
// fn default() -> Self {
// EditorContext {
// footprint: None,
// downstream_transform: None,
// real_time: None,
// animation_time: None,
// index: None,
// // editor_var_args: None,
// }
// }
// }
// impl EditorContext {
// pub fn to_context(&self) -> graphene_std::Context {
// let mut context = OwnedContextImpl::default();
// if let Some(footprint) = self.footprint {
// context.set_footprint(footprint);
// }
// if let Some(footprint) = self.footprint {
// context.set_footprint(footprint);
// }
// if let Some(downstream_transform) = self.downstream_transform {
// context.set_downstream_transform(downstream_transform);
// }
// if let Some(real_time) = self.real_time {
// context.set_real_time(real_time);
// }
// if let Some(animation_time) = self.animation_time {
// context.set_animation_time(animation_time);
// }
// if let Some(index) = self.index {
// context.set_index(index);
// }
// // if let Some(editor_var_args) = self.editor_var_args {
// // let (variable_names, values)
// // context.set_varargs((variable_names, values))
// // }
// context.into_context()
// }
// }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IntrospectError {
PathNotFound(Vec<NodeId>),
ProtoNodeNotFound(NodeId),
ProtoNodeNotFound(SNI),
NoData,
RuntimeNotReady,
IntrospectNotImplemented,
}
impl std::fmt::Display for IntrospectError {
@@ -154,6 +239,7 @@ impl std::fmt::Display for IntrospectError {
IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {:?}", id),
IntrospectError::NoData => write!(f, "No data found for this node"),
IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"),
IntrospectError::IntrospectNotImplemented => write!(f, "Intospect not implemented"),
}
}
}
@@ -178,55 +264,41 @@ impl std::fmt::Display for IntrospectError {
/// A store of the dynamically typed nodes and also the source map.
#[derive(Default, Clone)]
pub struct BorrowTree {
/// A hashmap of node IDs and dynamically typed nodes.
nodes: HashMap<NodeId, (SharedNodeContainer, Path)>,
/// A hashmap from the document path to the proto node ID.
source_map: HashMap<Path, (NodeId, NodeTypes)>,
// A hashmap of node IDs and dynamically typed nodes, as well as the number of inserted monitor nodes
nodes: HashMap<SNI, SharedNodeContainer>,
}
impl BorrowTree {
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
pub async fn new(proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
let mut nodes = BorrowTree::default();
for (id, node) in proto_network.nodes {
nodes.push_node(id, node, typing_context).await?
for node in proto_network {
nodes.push_node(node, typing_context).await?
}
Ok(nodes)
}
/// Pushes new nodes into the tree and return orphaned nodes
pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<Path>, HashSet<NodeId>), GraphErrors> {
let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect();
let mut new_nodes: Vec<_> = Vec::new();
// TODO: Problem: When an identity node is connected directly to an export the first input to identity node is not added to the proto network, while the second input is. This means the primary input does not have a type.
for (id, node) in proto_network.nodes {
if !self.nodes.contains_key(&id) {
new_nodes.push(node.original_location.path.clone().unwrap_or_default().into());
self.push_node(id, node, typing_context).await?;
} else if self.update_source_map(id, typing_context, &node) {
new_nodes.push(node.original_location.path.clone().unwrap_or_default().into());
/// Pushes new nodes into the tree and returns a vec of document nodes that had their types changed, and a vec of all nodes that were removed (including auto inserted value nodes)
pub async fn update(&mut self, proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<(Vec<SNI>, HashSet<SNI>), GraphErrors> {
let mut old_nodes = self.nodes.keys().copied().into_iter().collect::<HashSet<_>>();
// List of all document node paths that need to be updated, which occurs if their path changes or type changes
let mut nodes_with_new_type = Vec::new();
for node in proto_network {
let sni = node.stable_node_id;
old_nodes.remove(&sni);
let sni = node.stable_node_id;
if !self.nodes.contains_key(&sni) {
if node.original_location.send_types_to_editor {
nodes_with_new_type.push(sni)
}
self.push_node(node, typing_context);
}
old_nodes.remove(&id);
}
Ok((new_nodes, old_nodes))
Ok((nodes_with_new_type, old_nodes))
}
fn node_deps(&self, nodes: &[NodeId]) -> Vec<SharedNodeContainer> {
nodes.iter().map(|node| self.nodes.get(node).unwrap().0.clone()).collect()
}
fn store_node(&mut self, node: SharedNodeContainer, id: NodeId, path: Path) {
self.nodes.insert(id, (node, path));
}
/// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path.
pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
let (id, _) = self.source_map.get(node_path).ok_or_else(|| IntrospectError::PathNotFound(node_path.to_vec()))?;
let (node, _path) = self.nodes.get(id).ok_or(IntrospectError::ProtoNodeNotFound(*id))?;
node.serialize().ok_or(IntrospectError::NoData)
}
pub fn get(&self, id: NodeId) -> Option<SharedNodeContainer> {
self.nodes.get(&id).map(|(node, _)| node.clone())
fn node_deps(&self, nodes: &[SNI]) -> Vec<SharedNodeContainer> {
nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect()
}
/// Evaluate the output node of the [`BorrowTree`].
@@ -235,18 +307,18 @@ impl BorrowTree {
I: StaticType + 'i + Send + Sync,
O: StaticType + 'i,
{
let (node, _path) = self.nodes.get(&id).cloned()?;
let node = self.nodes.get(&id).cloned()?;
let output = node.eval(Box::new(input));
dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
}
/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value.
/// This ensures that no borrowed data can escape the node graph.
pub async fn eval_tagged_value<I>(&self, id: NodeId, input: I) -> Result<TaggedValue, String>
pub async fn eval_tagged_value<I>(&self, id: SNI, input: I) -> Result<TaggedValue, String>
where
I: StaticType + 'static + Send + Sync,
{
let (node, _path) = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
let output = node.eval(Box::new(input));
let inserted_node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
let output = inserted_node.eval(Box::new(input));
TaggedValue::try_from_any(output.await)
}
@@ -305,58 +377,12 @@ impl BorrowTree {
/// - Removes the node from `nodes` HashMap.
/// - If the node is the primary node for its path in the `source_map`, it's also removed from there.
/// - Returns `None` if the node is not found in the `nodes` HashMap.
pub fn free_node(&mut self, id: NodeId) -> Option<Path> {
let (_, path) = self.nodes.remove(&id)?;
if self.source_map.get(&path)?.0 == id {
self.source_map.remove(&path);
return Some(path);
pub fn free_node(&mut self, id: &SNI, inputs: usize) {
self.nodes.remove(&id);
// Also remove all corresponding monitor nodes
for monitor_index in 1..=inputs {
self.nodes.remove(&NodeId(id.0 + monitor_index as u64));
}
None
}
/// Updates the source map for a given node in the [`BorrowTree`].
///
/// This method updates or inserts an entry in the `source_map` HashMap for the specified node,
/// using type information from the provided [`TypingContext`] and [`ProtoNode`].
///
/// # Arguments
///
/// * `self` - Mutable reference to the [`BorrowTree`].
/// * `id` - The `NodeId` of the node to update in the source map.
/// * `typing_context` - A reference to the [`TypingContext`] containing type information.
/// * `proto_node` - A reference to the [`ProtoNode`] containing original location information.
///
/// # Returns
///
/// `bool` - `true` if a new entry was inserted, `false` if an existing entry was updated.
///
/// # Notes
///
/// - Updates or inserts an entry in the `source_map` HashMap.
/// - Uses the `ProtoNode`'s original location path as the key for the source map.
/// - Collects input types from both the main input and parameters.
/// - Returns `false` and logs a warning if the node's type information is not found in the typing context.
fn update_source_map(&mut self, id: NodeId, typing_context: &TypingContext, proto_node: &ProtoNode) -> bool {
let Some(node_io) = typing_context.type_of(id) else {
log::warn!("did not find type");
return false;
};
let inputs = [&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned().collect();
let node_path = &proto_node.original_location.path.as_ref().unwrap_or(const { &vec![] });
let entry = self.source_map.entry(node_path.to_vec().into()).or_default();
let update = (
id,
NodeTypes {
inputs,
output: node_io.return_value.clone(),
},
);
let modified = *entry != update;
*entry = update;
modified
}
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
@@ -374,53 +400,58 @@ impl BorrowTree {
/// - `Nodes`: Constructs a node using other nodes as dependencies.
/// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments.
/// - Returns an error if no constructor is found for the given node ID.
async fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> {
self.update_source_map(id, typing_context, &proto_node);
let path = proto_node.original_location.path.clone().unwrap_or_default();
match &proto_node.construction_args {
async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> {
let sni = proto_node.stable_node_id;
// Move the value into the upcast node instead of cloning it
match proto_node.construction_args {
ConstructionArgs::Value(value) => {
let node = if let TaggedValue::EditorApi(api) = &**value {
let editor_api = UpcastAsRefNode::new(api.clone());
let node = Box::new(editor_api) as TypeErasedBox<'_>;
NodeContainer::new(node)
} else {
let upcasted = UpcastNode::new(value.to_owned());
let node = Box::new(upcasted) as TypeErasedBox<'_>;
NodeContainer::new(node)
};
self.store_node(node, id, path.into());
// The constructor for nodes with value construction args (value nodes) is not called.
// It is not necessary to clone the Arc for the wasm editor api, since the value node is deduplicated and only called once.
// It is cloned whenever it is evaluated
let upcasted = UpcastNode::new(value);
let node = Box::new(upcasted) as TypeErasedBox<'_>;
self.nodes.insert(sni, NodeContainer::new(node));
}
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(ids) => {
let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect();
let construction_nodes = self.node_deps(&ids);
let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let node = constructor(construction_nodes).await;
ConstructionArgs::Nodes(ref node_construction_args) => {
let construction_nodes = self.node_deps(&node_construction_args.inputs);
let types = typing_context.type_of(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let monitor_nodes = construction_nodes
.into_iter()
.enumerate()
.map(|(input_index, construction_node)| {
let input_type = types.inputs.get(input_index).unwrap(); //.ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let monitor_constructor = typing_context.monitor_constructor(input_type).unwrap(); // .ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let monitor = monitor_constructor(construction_node);
let monitor_node_container = NodeContainer::new(monitor);
self.nodes.insert(NodeId(sni.0 + input_index as u64 + 1), monitor_node_container.clone());
monitor_node_container
})
.collect();
let constructor = typing_context.constructor(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let node = constructor(monitor_nodes).await;
let node = NodeContainer::new(node);
self.store_node(node, id, path.into());
self.nodes.insert(sni, node);
}
};
Ok(())
}
/// Returns the source map of the borrow tree
pub fn source_map(&self) -> &HashMap<Path, (NodeId, NodeTypes)> {
&self.source_map
}
}
#[cfg(test)]
mod test {
use super::*;
use graph_craft::document::value::TaggedValue;
use graphene_std::uuid::NodeId;
#[test]
fn push_node_sync() {
let mut tree = BorrowTree::default();
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![]);
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![], NodeId(0));
let context = TypingContext::default();
let future = tree.push_node(NodeId(0), val_1_protonode, &context);
let future = tree.push_node(val_1_protonode, &context);
futures::executor::block_on(future).unwrap();
let _node = tree.get(NodeId(0)).unwrap();
let result = futures::executor::block_on(tree.eval(NodeId(0), ()));
+2 -2
View File
@@ -43,8 +43,8 @@ mod tests {
use graph_craft::graphene_compiler::Compiler;
let compiler = Compiler {};
let protograph = compiler.compile_single(network).expect("Graph should be generated");
let protonetwork = network.flatten().map(|result| result.0).expect("Graph should be generated");
let _exec = block_on(DynamicExecutor::new(protograph)).map(|_e| panic!("The network should not type check ")).unwrap_err();
let _exec = block_on(DynamicExecutor::new(protonetwork)).map(|_e| panic!("The network should not type check ")).unwrap_err();
}
}
@@ -1,7 +1,7 @@
use dyn_any::StaticType;
use glam::{DVec2, IVec2, UVec2};
use graph_craft::document::value::RenderOutput;
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graph_craft::proto::{MonitorConstructor, NodeConstructor, TypeErasedBox};
use graphene_core::raster::color::Color;
use graphene_core::raster::*;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable};
@@ -18,7 +18,7 @@ use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
#[cfg(feature = "gpu")]
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
use node_registry_macros::{async_node, convert_node, into_node};
use node_registry_macros::{async_node, convert_node, into_node, monitor_node};
use once_cell::sync::Lazy;
use std::collections::HashMap;
#[cfg(feature = "gpu")]
@@ -192,6 +192,52 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
pub static NODE_REGISTRY: Lazy<HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>> = Lazy::new(|| node_registry());
fn monitor_nodes() -> HashMap<Type, MonitorConstructor> {
let nodes: Vec<(Type, MonitorConstructor)> = vec![
monitor_node!(ImageTexture),
monitor_node!(VectorDataTable),
monitor_node!(GraphicGroupTable),
monitor_node!(GraphicElement),
monitor_node!(Artboard),
monitor_node!(RasterDataTable<CPU>),
monitor_node!(RasterDataTable<GPU>),
monitor_node!(graphene_core::instances::Instances<Artboard>),
monitor_node!(String),
monitor_node!(IVec2),
monitor_node!(DVec2),
monitor_node!(bool),
monitor_node!(f64),
monitor_node!(u32),
monitor_node!(u64),
monitor_node!(()),
monitor_node!(Vec<f64>),
monitor_node!(BlendMode),
monitor_node!(graphene_std::transform::ReferencePoint),
monitor_node!(graphene_path_bool::BooleanOperation),
monitor_node!(Option<Color>),
monitor_node!(graphene_core::vector::style::Fill),
monitor_node!(graphene_core::vector::style::StrokeCap),
monitor_node!(graphene_core::vector::style::StrokeJoin),
monitor_node!(graphene_core::vector::style::PaintOrder),
monitor_node!(graphene_core::vector::style::StrokeAlign),
monitor_node!(graphene_core::vector::style::Stroke),
monitor_node!(graphene_core::vector::style::Gradient),
monitor_node!(graphene_core::vector::style::GradientStops),
monitor_node!(Vec<graphene_core::uuid::NodeId>),
monitor_node!(Color),
monitor_node!(Box<graphene_core::vector::VectorModification>),
monitor_node!(graphene_std::vector::misc::CentroidType),
monitor_node!(graphene_std::vector::misc::PointSpacingType),
];
let mut monitor_nodes = HashMap::new();
for (monitor_type, constructor) in nodes {
monitor_nodes.insert(monitor_type, constructor);
}
monitor_nodes
}
pub static MONITOR_NODES: Lazy<HashMap<Type, MonitorConstructor>> = Lazy::new(|| monitor_nodes());
mod node_registry_macros {
macro_rules! async_node {
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
@@ -207,7 +253,7 @@ mod node_registry_macros {
|mut args| {
Box::pin(async move {
args.reverse();
let node = <$path>::new($(graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let node = <$path>::new($(graphene_std::registry::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
@@ -285,7 +331,18 @@ mod node_registry_macros {
};
}
macro_rules! monitor_node {
($type:ty) => {
(concrete!($type), |arg| {
let node = <graphene_core::memo::MonitorNode<graphene_std::Context, _, _>>::new(graphene_std::registry::downcast_node::<graphene_std::Context, $type>(arg));
let any: DynAnyNode<_, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
};
}
pub(crate) use async_node;
pub(crate) use convert_node;
pub(crate) use into_node;
pub(crate) use monitor_node;
}
+2 -19
View File
@@ -8,22 +8,13 @@ use graphene_std::Context;
use graphene_std::uuid::NodeId;
use std::sync::Arc;
// TODO: this is copy pasta from the editor (and does get out of sync)
pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEditorApi>) -> NodeNetwork {
network.generate_node_paths(&[]);
let inner_network = DocumentNode {
implementation: DocumentNodeImplementation::Network(network),
inputs: vec![],
..Default::default()
};
// TODO: Replace with "Output" definition?
// let render_node = resolve_document_node_type("Output")
// .expect("Output node type not found")
// .node_template_input_override(vec![Some(NodeInput::node(NodeId(1), 0)), Some(NodeInput::node(NodeId(0), 1))])
// .document_node;
let render_node = DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::Network(NodeNetwork {
@@ -64,20 +55,12 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
};
// wrap the inner network in a scope
let nodes = vec![
inner_network,
render_node,
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)],
..Default::default()
},
];
let nodes = vec![inner_network, render_node];
NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(),
scope_injections: [("editor-api".to_string(), (NodeId(2), concrete!(&WasmEditorApi)))].into_iter().collect(),
scope_injections: [("editor-api".to_string(), TaggedValue::EditorApi(editor_api))].into_iter().collect(),
// TODO(TrueDoctor): check if it makes sense to set `generated` to `true`
generated: false,
}
+1
View File
@@ -3,6 +3,7 @@ use graph_craft::document::*;
use graph_craft::proto::RegistryValueSource;
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::registry::*;
use graphene_std::uuid::NodeId;
use graphene_std::*;
use std::collections::{HashMap, HashSet};