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)] #[derive(Debug, Default)]
pub struct Dispatcher { pub struct Dispatcher {
buffered_queue: Option<Vec<VecDeque<Message>>>, buffered_queue: Vec<Message>,
queueing_messages: bool,
message_queues: Vec<VecDeque<Message>>, message_queues: Vec<VecDeque<Message>>,
pub responses: Vec<FrontendMessage>, pub responses: Vec<FrontendMessage>,
pub message_handlers: DispatcherMessageHandlers, 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) { pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
let message = message.into(); let message = message.into();
// Add all additional messages to the buffer if it exists (except from the end buffer message) // Add all additional messages to the queue if it exists (except from the end queue message)
if !matches!(message, Message::EndBuffer { .. }) { if !matches!(message, Message::EndQueue) {
if let Some(buffered_queue) = &mut self.buffered_queue { if self.queueing_messages {
Self::schedule_execution(buffered_queue, true, [message]); self.buffered_queue.push(message);
return; 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 // Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
match message { 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) => { Message::Animation(message) => {
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ()); self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
} }
@@ -84,7 +84,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
} }
AnimationMessage::SetFrameIndex { frame } => { AnimationMessage::SetFrameIndex { frame } => {
self.frame_index = frame; self.frame_index = frame;
responses.add(PortfolioMessage::SubmitActiveGraphRender); responses.add(PortfolioMessage::EvaluateActiveDocument);
// Update the restart and pause/play buttons // Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
@@ -100,7 +100,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
} }
AnimationMessage::UpdateTime => { AnimationMessage::UpdateTime => {
if self.is_playing() { if self.is_playing() {
responses.add(PortfolioMessage::SubmitActiveGraphRender); responses.add(PortfolioMessage::EvaluateActiveDocument);
if self.live_preview_recently_zero { if self.live_preview_recently_zero {
// Update the restart and pause/play buttons // Update the restart and pause/play buttons
@@ -116,7 +116,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
_ => AnimationState::Stopped, _ => AnimationState::Stopped,
}; };
self.live_preview_recently_zero = true; self.live_preview_recently_zero = true;
responses.add(PortfolioMessage::SubmitActiveGraphRender); responses.add(PortfolioMessage::EvaluateActiveDocument);
// Update the restart and pause/play buttons // Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
@@ -43,7 +43,7 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background, ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background,
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area, 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_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
file_type: self.file_type, file_type: self.file_type,
scale_factor: self.scale_factor, 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; let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
if create_artboard { if create_artboard {
responses.add(Message::StartBuffer); responses.add(Message::StartQueue);
responses.add(GraphOperationMessage::NewArtboard { responses.add(GraphOperationMessage::NewArtboard {
id: NodeId::new(), id: NodeId::new(),
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()), 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 // 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 // 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(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
} }
@@ -174,7 +174,7 @@ impl PreferencesDialogMessageHandler {
let use_vello = vec![ let use_vello = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
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) .tooltip(vello_tooltip)
.disabled(!preferences.supports_wgpu()) .disabled(!preferences.supports_wgpu())
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into()) .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)] #[impl_message(Message, Frontend)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendMessage { pub enum FrontendMessage {
ClearNodeThumbnail {
sni: NodeId,
},
// Display prefix: make the frontend show something, like a dialog // Display prefix: make the frontend show something, like a dialog
DisplayDialog { DisplayDialog {
title: String, title: String,
@@ -272,10 +275,6 @@ pub enum FrontendMessage {
UpdateNodeGraphTransform { UpdateNodeGraphTransform {
transform: Transform, transform: Transform,
}, },
UpdateNodeThumbnail {
id: NodeId,
value: String,
},
UpdateOpenDocumentsList { UpdateOpenDocumentsList {
#[serde(rename = "openDocuments")] #[serde(rename = "openDocuments")]
open_documents: Vec<FrontendDocumentDetails>, open_documents: Vec<FrontendDocumentDetails>,
@@ -285,6 +284,11 @@ pub enum FrontendMessage {
layout_target: LayoutTarget, layout_target: LayoutTarget,
diff: Vec<WidgetDiff>, diff: Vec<WidgetDiff>,
}, },
UpdateThumbnails {
add: Vec<(NodeId, String)>,
clear: Vec<NodeId>,
// remove: Vec<NodeId>,
},
UpdateToolOptionsLayout { UpdateToolOptionsLayout {
#[serde(rename = "layoutTarget")] #[serde(rename = "layoutTarget")]
layout_target: LayoutTarget, layout_target: LayoutTarget,
@@ -14,7 +14,7 @@ pub struct InputPreprocessorMessageContext {
#[derive(Debug, Default, ExtractField)] #[derive(Debug, Default, ExtractField)]
pub struct InputPreprocessorMessageHandler { pub struct InputPreprocessorMessageHandler {
pub frame_time: FrameTimeInfo, pub frame_time: FrameTimeInfo,
pub time: u64, pub time: f64,
pub keyboard: KeyStates, pub keyboard: KeyStates,
pub mouse: MouseState, pub mouse: MouseState,
pub viewport_bounds: ViewportBounds, pub viewport_bounds: ViewportBounds,
@@ -98,9 +98,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
self.translate_mouse_event(mouse_state, false, responses); self.translate_mouse_event(mouse_state, false, responses);
} }
InputPreprocessorMessage::CurrentTime { timestamp } => { InputPreprocessorMessage::CurrentTime { timestamp } => {
responses.add(AnimationMessage::SetTime { time: timestamp as f64 }); self.time = timestamp as f64;
self.time = timestamp;
self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
} }
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => { InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); 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 // IPP bounds are relative to the entire application
[(0., 0.).into(), self.viewport_bounds.bottom_right - self.viewport_bounds.top_left] [(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)] #[cfg(test)]
+19 -3
View File
@@ -1,11 +1,27 @@
use std::sync::Arc;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graphene_std::renderer::RenderMetadata; use graphene_std::{IntrospectMode, uuid::CompiledProtonodeInput};
use graphite_proc_macros::*; use graphite_proc_macros::*;
#[impl_message] #[impl_message]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, PartialEq)]
pub enum Message { 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] #[child]
Animation(AnimationMessage), Animation(AnimationMessage),
#[child] #[child]
@@ -7,12 +7,13 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
use crate::messages::portfolio::utility_types::PanelType; use crate::messages::portfolio::utility_types::PanelType;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use glam::DAffine2; use glam::DAffine2;
use graph_craft::document::NodeId; use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::Color; use graphene_std::Color;
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster::Image; use graphene_std::raster::Image;
use graphene_std::renderer::ClickTarget;
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::click_target::ClickTarget; use graphene_std::uuid::NodeId;
use graphene_std::vector::style::ViewMode; use graphene_std::vector::style::ViewMode;
#[impl_message(Message, PortfolioMessage, Document)] #[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::path_bool::{boolean_intersect, path_bool_lib};
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{Raster, RasterDataTable}; use graphene_std::raster_types::{Raster, RasterDataTable};
use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId; use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode; use graphene_std::vector::style::ViewMode;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
#[derive(ExtractField)] #[derive(ExtractField)]
@@ -43,10 +45,11 @@ pub struct DocumentMessageContext<'a> {
pub document_id: DocumentId, pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler, pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData, pub persistent_data: &'a PersistentData,
pub executor: &'a mut NodeGraphExecutor,
pub current_tool: &'a ToolType, pub current_tool: &'a ToolType,
pub preferences: &'a PreferencesMessageHandler, pub preferences: &'a PreferencesMessageHandler,
pub device_pixel_ratio: f64, 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)] #[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 /// 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)] #[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. /// Path to network that is currently selected. Updated based on the most recently clicked panel.
#[serde(skip)] #[serde(skip)]
selection_network_path: Vec<NodeId>, pub selection_network_path: Vec<NodeId>,
/// Stack of document network snapshots for previous history states. /// Stack of document network snapshots for previous history states.
#[serde(skip)] #[serde(skip)]
document_undo_history: VecDeque<NodeNetworkInterface>, document_undo_history: VecDeque<NodeNetworkInterface>,
@@ -176,11 +179,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
document_id, document_id,
ipp, ipp,
persistent_data, persistent_data,
executor,
current_tool, current_tool,
preferences, preferences,
device_pixel_ratio, 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_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(); 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], node_ids: vec![node_id],
delete_children: true, delete_children: true,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::EndTransaction); 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(); let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
DocumentMessage::EnterNestedNetwork { node_id } => { DocumentMessage::EnterNestedNetwork { node_id } => {
self.breadcrumb_network_path.push(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); responses.add(NodeGraphMessage::SendGraph);
} }
DocumentMessage::MoveSelectedLayersToGroup { parent } => { 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::SelectedNodesSet { nodes: vec![parent.to_node()] });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(DocumentMessage::DocumentStructureChanged); responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
@@ -1152,7 +1156,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
DocumentMessage::SetNodePinned { node_id, pinned } => { DocumentMessage::SetNodePinned { node_id, pinned } => {
responses.add(DocumentMessage::AddTransaction); responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::SetPinned { node_id, pinned }); responses.add(NodeGraphMessage::SetPinned { node_id, pinned });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
@@ -1215,7 +1219,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
} }
DocumentMessage::SetViewMode { view_mode } => { DocumentMessage::SetViewMode { view_mode } => {
self.view_mode = view_mode; self.view_mode = view_mode;
responses.add_front(NodeGraphMessage::RunDocumentGraph); responses.add_front(PortfolioMessage::CompileActiveDocument);
} }
DocumentMessage::AddTransaction => { DocumentMessage::AddTransaction => {
// Reverse order since they are added to the front // 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; self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
responses.add(PortfolioMessage::UpdateDocumentWidgets); 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 { DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints, upstream_footprints,
local_transforms, local_transforms,
@@ -1364,7 +1379,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(DocumentMessage::UngroupLayer { layer: folder }); responses.add(DocumentMessage::UngroupLayer { layer: folder });
} }
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(DocumentMessage::DocumentStructureChanged); responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
@@ -1402,7 +1417,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
node_ids: vec![layer.to_node()], node_ids: vec![layer.to_node()],
delete_children: true, delete_children: true,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
@@ -1417,7 +1432,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
center: Key::Alt, center: Key::Alt,
duplicate: Key::Alt, duplicate: Key::Alt,
})); }));
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} else { } else {
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else { let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
return; 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 // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
// TODO: Remove once the footprint is used to load the imports/export distances from the edge // TODO: Remove once the footprint is used to load the imports/export distances from the edge
responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(Message::StartBuffer); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartQueue);
Some(previous_network) Some(previous_network)
} }
pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) { 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 // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::ForceRunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires); responses.add(NodeGraphMessage::SendWires);
Some(previous_network) 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) { 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), &[]); 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(); return only_selected_layer.to_node();
} }
@@ -2874,7 +2889,7 @@ impl DocumentMessageHandler {
} }
if modified { if modified {
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
} }
@@ -126,7 +126,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
} }
} }
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] }); responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
GraphOperationMessage::NewBitmapLayer { GraphOperationMessage::NewBitmapLayer {
id, id,
@@ -138,7 +138,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
let layer = modify_inputs.create_layer(id); let layer = modify_inputs.create_layer(id);
modify_inputs.insert_image_data(image_frame, layer); modify_inputs.insert_image_data(image_frame, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]); 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 } => { GraphOperationMessage::NewBooleanOperationLayer { id, operation, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses); let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
@@ -149,7 +149,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
node_id: id, node_id: id,
alias: "Boolean Operation".to_string(), alias: "Boolean Operation".to_string(),
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => { GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses); 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 // Move the layer and all nodes to the correct position in the network
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index }); responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => { GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses); let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id); let layer = modify_inputs.create_layer(id);
modify_inputs.insert_vector_data(subpaths, layer, true, true, true); modify_inputs.insert_vector_data(subpaths, layer, true, true, true);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]); network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
GraphOperationMessage::NewTextLayer { GraphOperationMessage::NewTextLayer {
id, id,
@@ -191,7 +191,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
modify_inputs.insert_text(text, font, typesetting, layer); modify_inputs.insert_text(text, font, typesetting, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]); network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke: Stroke::default() }); responses.add(GraphOperationMessage::StrokeSet { layer, stroke: Stroke::default() });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
GraphOperationMessage::ResizeArtboard { layer, location, dimensions } => { GraphOperationMessage::ResizeArtboard { layer, location, dimensions } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { 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::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
@@ -454,7 +454,7 @@ impl<'a> ModifyInputsContext<'a> {
// Refresh the render and editor UI // Refresh the render and editor UI
self.responses.add(PropertiesPanelMessage::Refresh); self.responses.add(PropertiesPanelMessage::Refresh);
if !skip_rerender { 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 }; let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
self.network_interface.vector_modify(&path_node_id, modification_type); self.network_interface.vector_modify(&path_node_id, modification_type);
self.responses.add(PropertiesPanelMessage::Refresh); self.responses.add(PropertiesPanelMessage::Refresh);
self.responses.add(NodeGraphMessage::RunDocumentGraph); self.responses.add(PortfolioMessage::CompileActiveDocument);
} }
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) { 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.network_interface.set_input(&input_connector, input, &[]);
self.responses.add(PropertiesPanelMessage::Refresh); self.responses.add(PropertiesPanelMessage::Refresh);
if !skip_rerender { 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 struct NodePropertiesContext<'a> {
pub persistent_data: &'a PersistentData, pub persistent_data: &'a PersistentData,
pub responses: &'a mut VecDeque<Message>, pub responses: &'a mut VecDeque<Message>,
pub executor: &'a mut NodeGraphExecutor,
pub network_interface: &'a mut NodeNetworkInterface, pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId], pub selection_network_path: &'a [NodeId],
pub document_name: &'a str, pub document_name: &'a str,
@@ -208,12 +208,6 @@ pub enum NodeGraphMessage {
UpdateImportsExports, UpdateImportsExports,
UpdateLayerPanel, UpdateLayerPanel,
UpdateNewNodeGraph, UpdateNewNodeGraph,
UpdateTypes {
#[serde(skip)]
resolved_types: ResolvedDocumentNodeTypesDelta,
#[serde(skip)]
node_graph_errors: GraphErrors,
},
UpdateActionButtons, UpdateActionButtons,
UpdateGraphBarRight, UpdateGraphBarRight,
UpdateInSelectedNetwork, UpdateInSelectedNetwork,
@@ -180,7 +180,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(DocumentMessage::AddTransaction); responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer }); responses.add(NodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer });
responses.add(PropertiesPanelMessage::Refresh); responses.add(PropertiesPanelMessage::Refresh);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
NodeGraphMessage::CreateNodeFromContextMenu { NodeGraphMessage::CreateNodeFromContextMenu {
node_id, node_id,
@@ -241,7 +241,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
input_connector: InputConnector::node(node_id, input_index), input_connector: InputConnector::node(node_id, input_index),
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
self.wire_in_progress_from_connector = None; 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<_>>(), node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),
delete_children, delete_children,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph); 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::SelectedNodesSet { nodes: vec![encapsulating_node_id] });
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index } => { NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index } => {
network_interface.move_layer_to_stack(layer, parent, insert_index, selection_network_path); 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 }); responses.add(NodeGraphMessage::DisconnectInput { input_connector: *disconnecting });
} }
// Update the frontend that the node is disconnected // Update the frontend that the node is disconnected
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
self.disconnecting = None; self.disconnecting = None;
} }
@@ -1064,7 +1064,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
output_connector: *output_connector, output_connector: *output_connector,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} else if output_connector.is_some() && input_connector.is_none() && !self.initial_disconnecting { } 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, input_connector: *overlapping_wire,
insert_node_input_index: selected_node_input_connect_index, insert_node_input_index: selected_node_input_connect_index,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
} }
@@ -1273,24 +1273,24 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
NodeGraphMessage::RemoveImport { import_index: usize } => { NodeGraphMessage::RemoveImport { import_index: usize } => {
network_interface.remove_import(usize, selection_network_path); network_interface.remove_import(usize, selection_network_path);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
NodeGraphMessage::RemoveExport { export_index: usize } => { NodeGraphMessage::RemoveExport { export_index: usize } => {
network_interface.remove_export(usize, selection_network_path); network_interface.remove_export(usize, selection_network_path);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
NodeGraphMessage::ReorderImport { start_index, end_index } => { NodeGraphMessage::ReorderImport { start_index, end_index } => {
network_interface.reorder_import(start_index, end_index, selection_network_path); network_interface.reorder_import(start_index, end_index, selection_network_path);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
NodeGraphMessage::ReorderExport { start_index, end_index } => { NodeGraphMessage::ReorderExport { start_index, end_index } => {
network_interface.reorder_export(start_index, end_index, selection_network_path); network_interface.reorder_export(start_index, end_index, selection_network_path);
responses.add(NodeGraphMessage::SendGraph); 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 }); responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
} }
NodeGraphMessage::ForceRunDocumentGraph => { 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 { let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
return; return;
}; };
let document_bbox: [DVec2; 2] = ipp.document_bounds();
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 mut nodes = Vec::new(); let mut nodes = Vec::new();
for node_id in &self.frontend_nodes { for node_id in &self.frontend_nodes {
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else { 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); 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) { 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 } => { 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)) { 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 } => { NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
@@ -1486,7 +1483,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(FrontendMessage::UpdateContextMenuInformation { responses.add(FrontendMessage::UpdateContextMenuInformation {
context_menu_information: self.context_menu.clone(), context_menu_information: self.context_menu.clone(),
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::SendWires); responses.add(NodeGraphMessage::SendWires);
} }
@@ -1521,7 +1518,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(DocumentMessage::AddTransaction); responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::TogglePreviewImpl { node_id }); responses.add(NodeGraphMessage::TogglePreviewImpl { node_id });
responses.add(NodeGraphMessage::UpdateActionButtons); responses.add(NodeGraphMessage::UpdateActionButtons);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
NodeGraphMessage::TogglePreviewImpl { node_id } => { NodeGraphMessage::TogglePreviewImpl { node_id } => {
network_interface.toggle_preview(node_id, selection_network_path); 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 } => { NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids } => {
if node_ids.iter().any(|node_id| network_interface.connected_to_output(node_id, selection_network_path)) { 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::UpdateActionButtons);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
@@ -1717,15 +1714,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::SendGraph); 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 => { NodeGraphMessage::UpdateActionButtons => {
if selection_network_path == breadcrumb_network_path { if selection_network_path == breadcrumb_network_path {
self.update_graph_bar_left(network_interface, breadcrumb_network_path, responses); 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::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::node_graph_executor::NodeGraphExecutor;
#[derive(ExtractField)] use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
pub struct PropertiesPanelMessageContext<'a> { 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 network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId], pub selection_network_path: &'a [NodeId],
pub document_name: &'a str, pub document_name: &'a str,
pub executor: &'a mut NodeGraphExecutor,
pub persistent_data: &'a PersistentData,
} }
#[derive(Debug, Clone, Default, ExtractField)] #[derive(Debug, Clone, Default, ExtractField)]
@@ -26,9 +32,7 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
network_interface, network_interface,
selection_network_path, selection_network_path,
document_name, document_name,
executor, } = data;
persistent_data,
} = context;
match message { match message {
PropertiesPanelMessage::Clear => { PropertiesPanelMessage::Clear => {
@@ -44,7 +48,6 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
network_interface, network_interface,
selection_network_path, selection_network_path,
document_name, document_name,
executor,
}; };
let properties_sections = NodeGraphMessageHandler::collate_properties(&mut node_properties_context); 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 bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue; 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 graph_craft::{Type, concrete};
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId};
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData, VectorModificationType}; use graphene_std::vector::{PointId, VectorData, VectorModificationType};
use graphene_std::{CompiledProtonodeInput, SNI};
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes; use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
use interpreted_executor::node_registry::NODE_REGISTRY; use interpreted_executor::node_registry::NODE_REGISTRY;
use serde_json::{Value, json}; use serde_json::{Value, json};
@@ -40,6 +42,8 @@ pub struct NodeNetworkInterface {
pub resolved_types: ResolvedDocumentNodeTypes, pub resolved_types: ResolvedDocumentNodeTypes,
#[serde(skip)] #[serde(skip)]
transaction_status: TransactionStatus, transaction_status: TransactionStatus,
#[serde(skip)]
current_hash: u64,
} }
impl Clone for NodeNetworkInterface { impl Clone for NodeNetworkInterface {
@@ -478,6 +482,13 @@ impl NodeNetworkInterface {
node_template 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 /// Try and get the [`DocumentNodeDefinition`] for a node
pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> { pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> {
let metadata = self.node_metadata(&node_id, network_path)?; 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`. pub fn downstream_caller_from_output(&self, output_connector: OutputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> {
fn node_type_from_compiled(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<(Type, TypeSource)> { match output_connector {
let (node_id, input_index) = match *input_connector { OutputConnector::Node { node_id, output_index } => match self.implementation(&node_id, network_path)? {
InputConnector::Node { node_id, input_index } => (node_id, input_index), DocumentNodeImplementation::Network(node_network) => {
InputConnector::Export(export_index) => { let mut nested_path = network_path.to_vec();
let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else { nested_path.push(node_id);
// The outermost network export defaults to an ArtboardGroupTable. self.downstream_caller_from_input(InputConnector::Export(output_index), &nested_path)
return Some((concrete!(graphene_std::ArtboardGroupTable), TypeSource::OuterMostExportDefault)); }
}; DocumentNodeImplementation::ProtoNode(_) => self.node_metadata(&node_id, network_path)?.transient_metadata.caller.as_ref(),
DocumentNodeImplementation::Extract => todo!(),
let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path); },
return Some(output_type); 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 { let Some(caller_input) = caller_input else {
log::error!("Could not get node {node_id} in input_type"); log::error!("Could not get compiled caller input for input: {:?}", input_connector);
return None; return None;
}; };
// If the input_connector is a NodeInput::Value, return the type of the tagged value. Some(caller_input)
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();
if inputs_using_import.is_some_and(|inputs| inputs.len() > 1) { pub fn take_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<NodeInput> {
warn!("Found multiple inputs using an import. Using the type of the first one."); let Some(network) = self.network_mut(network_path) else {
} log::error!("Could not get network in input_from_connector");
return None;
if let Some(input_connector) = first_input { };
self.node_type_from_compiled(&input_connector, &node_id_path) let input = match input_connector {
} InputConnector::Node { node_id, input_index } => {
// Nothing is connected to the import let Some(node) = network.nodes.get_mut(node_id) else {
else { log::error!("Could not get node {node_id} in input_from_connector");
None return None;
} };
node.inputs.get_mut(*input_index)
} }
DocumentNodeImplementation::ProtoNode(_) => { InputConnector::Export(export_index) => network.exports.get_mut(*export_index),
// 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 }; input.map(|input| std::mem::replace(input, NodeInput::value(TaggedValue::None, true)))
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,
}
} }
/// Guess the type from the node based on a document node default or a random protonode definition. /// 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 // Try and get the default value from the document node definition
if let Some(value) = self 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(|definition| definition.node_template.document_node.inputs.get(input_index))
.and_then(|input| input.as_value()) .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:?}")); 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 { match &node.implementation {
DocumentNodeImplementation::ProtoNode(protonode) => { DocumentNodeImplementation::ProtoNode(protonode) => {
let Some(node_types) = random_protonode_implementation(protonode) else { let Some(node_types) = random_protonode_implementation(protonode) else {
return (concrete!(()), TypeSource::Error("could not resolve protonode")); return (concrete!(()), TypeSource::Error("could not resolve protonode"));
}; };
let skip_footprint = if node.manual_composition.is_some() { 1 } else { 0 }; let Some(input_type) = node_types.inputs.get(input_index) else {
let Some(input_type) = std::iter::once(node_types.call_argument.clone()).chain(node_types.inputs.clone()).nth(input_index + skip_footprint) else {
log::error!("Could not get type"); log::error!("Could not get type");
return (concrete!(()), TypeSource::Error("could not get the protonode's input")); return (concrete!(()), TypeSource::Error("could not get the protonode's input"));
}; };
(input_type, TypeSource::RandomProtonodeImplementation) (input_type.clone(), TypeSource::RandomProtonodeImplementation)
} }
DocumentNodeImplementation::Network(_network) => { DocumentNodeImplementation::Network(_network) => {
// Attempt to resolve where this import is within the nested network // Attempt to resolve where this import is within the nested network
@@ -598,9 +629,10 @@ impl NodeNetworkInterface {
input_index: child_input_index, input_index: child_input_index,
}) = first_input }) = first_input
{ {
network_path.push(node_id); let mut inner_path = network_path.to_vec();
let result = self.guess_type_from_node(network_path, child_id, child_input_index); inner_path.push(node_id);
network_path.pop(); let result = self.guess_type_from_node(child_id, child_input_index, inner_path);
inner_path.pop();
return result; return result;
} }
@@ -613,8 +645,11 @@ impl NodeNetworkInterface {
/// Get the [`Type`] for any InputConnector /// Get the [`Type`] for any InputConnector
pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) { 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) { if let Some(compiled_type) = self
return result; .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 // Resolve types from proto nodes in node_registry
@@ -622,9 +657,57 @@ impl NodeNetworkInterface {
return (concrete!(()), TypeSource::Error("input connector is not a node")); 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), self.guess_type_from_node(node_id, input_connector.input_index(), network_path);
// 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())
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> { 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(); let mut node_metadata = DocumentNodeMetadata::default();
node.inputs = old_node.inputs; node.inputs = old_node.inputs;
node.manual_composition = old_node.manual_composition;
node.visible = old_node.visible; 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.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.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; node_metadata.persistent_metadata.has_primary_output = old_node.has_primary_output;
@@ -1539,7 +1619,7 @@ impl NodeNetworkInterface {
network: node_network, network: node_network,
network_metadata, network_metadata,
document_metadata: DocumentMetadata::default(), document_metadata: DocumentMetadata::default(),
resolved_types: ResolvedDocumentNodeTypes::default(), resolved_types: HashMap::new(),
transaction_status: TransactionStatus::Finished, transaction_status: TransactionStatus::Finished,
} }
} }
@@ -6060,94 +6140,6 @@ pub enum ImportOrExport {
Export(usize), 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)] #[derive(Debug, Clone)]
pub struct Ports { pub struct Ports {
input_ports: Vec<(usize, ClickTarget)>, input_ports: Vec<(usize, ClickTarget)>,
@@ -6381,6 +6373,7 @@ pub struct NodeNetworkTransientMetadata {
pub rounded_network_edge_distance: TransientMetadata<NetworkEdgeDistance>, pub rounded_network_edge_distance: TransientMetadata<NetworkEdgeDistance>,
// Wires from the exports // Wires from the exports
pub wires: Vec<TransientMetadata<WirePathUpdate>>, pub wires: Vec<TransientMetadata<WirePathUpdate>>,
pub callers: Vec<Option<CompiledProtonodeInput>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -6568,8 +6561,8 @@ impl InputPersistentMetadata {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct InputTransientMetadata { struct InputTransientMetadata {
wire: TransientMetadata<WirePathUpdate>, wire: TransientMetadata<WirePathUpdate>,
// downstream_protonode: populated for all inputs after each compile caller: Option<CompiledProtonodeInput>,
// types: populated for each protonode after each input_type: Option<Type>,
} }
// TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
@@ -6883,6 +6876,8 @@ pub struct DocumentNodeTransientMetadata {
pub click_targets: TransientMetadata<DocumentNodeClickTargets>, 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. // 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, 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)] #[derive(Debug, Clone)]
@@ -2,7 +2,8 @@ use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use super::network_interface::NodeNetworkInterface; use super::network_interface::NodeNetworkInterface;
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::DVec2; use glam::DVec2;
use graph_craft::document::{NodeId, NodeNetwork}; use graph_craft::document::NodeNetwork;
use graphene_std::uuid::NodeId;
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)] #[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::document::utility_types::document_metadata::LayerNodeIdentifier;
use super::utility_types::PanelType; use super::utility_types::PanelType;
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*; 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::raster::Image;
use graphene_std::renderer::RenderMetadata;
use graphene_std::text::Font; use graphene_std::text::Font;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::{Color, IntrospectMode};
#[impl_message(Message, Portfolio)] #[impl_message(Message, Portfolio)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -18,8 +24,24 @@ pub enum PortfolioMessage {
#[child] #[child]
Spreadsheet(SpreadsheetMessage), Spreadsheet(SpreadsheetMessage),
// Messages // Sends a request to compile the network. Should occur when any value, preference, or font changes
Init, 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 { DocumentPassMessage {
document_id: DocumentId, document_id: DocumentId,
message: DocumentMessage, message: DocumentMessage,
@@ -48,7 +70,6 @@ pub enum PortfolioMessage {
document_id: DocumentId, document_id: DocumentId,
}, },
DestroyAllDocuments, DestroyAllDocuments,
EditorPreferences,
FontLoaded { FontLoaded {
font_family: String, font_family: String,
font_style: String, font_style: String,
@@ -120,13 +141,7 @@ pub enum PortfolioMessage {
bounds: ExportBounds, bounds: ExportBounds,
transparent_background: bool, transparent_background: bool,
}, },
SubmitActiveGraphRender,
SubmitGraphRender {
document_id: DocumentId,
ignore_hash: bool,
},
ToggleRulers, ToggleRulers,
UpdateDocumentWidgets, UpdateDocumentWidgets,
UpdateOpenDocumentsList, UpdateOpenDocumentsList,
UpdateVelloPreference,
} }
@@ -3,19 +3,18 @@ use super::document::utility_types::network_interface;
use super::spreadsheet::SpreadsheetMessageHandler; use super::spreadsheet::SpreadsheetMessageHandler;
use super::utility_types::{PanelType, PersistentData}; use super::utility_types::{PanelType, PersistentData};
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::DEFAULT_DOCUMENT_NAME; use crate::consts::{DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX};
use crate::messages::animation::TimingInformation;
use crate::messages::debug::utility_types::MessageLoggingVerbosity; use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::simple_dialogs; use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails; use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageContext; use crate::messages::portfolio::document::DocumentMessageContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; 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::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::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document_migration::*; use crate::messages::portfolio::document_migration::*;
use crate::messages::portfolio::spreadsheet::{InspectInputConnector, SpreadsheetMessageHandlerData};
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
@@ -45,6 +44,7 @@ pub struct PortfolioMessageHandler {
active_panel: PanelType, active_panel: PanelType,
pub(crate) active_document_id: Option<DocumentId>, pub(crate) active_document_id: Option<DocumentId>,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize], copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
// Data that persists between documents
pub persistent_data: PersistentData, pub persistent_data: PersistentData,
pub executor: NodeGraphExecutor, pub executor: NodeGraphExecutor,
pub selection_mode: SelectionMode, pub selection_mode: SelectionMode,
@@ -52,6 +52,11 @@ pub struct PortfolioMessageHandler {
pub spreadsheet: SpreadsheetMessageHandler, pub spreadsheet: SpreadsheetMessageHandler,
device_pixel_ratio: Option<f64>, device_pixel_ratio: Option<f64>,
pub reset_node_definitions_on_open: bool, 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] #[message_handler_data]
@@ -100,7 +105,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
self.menu_bar_message_handler.process_message(message, responses, ()); self.menu_bar_message_handler.process_message(message, responses, ());
} }
PortfolioMessage::Spreadsheet(message) => { PortfolioMessage::Spreadsheet(message) => {
self.spreadsheet.process_message(message, responses, ()); self.spreadsheet.process_message(message, responses, SpreadsheetMessageHandlerData {introspected_data});
} }
PortfolioMessage::Document(message) => { PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id { if let Some(document_id) = self.active_document_id {
@@ -109,7 +114,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_id, document_id,
ipp, ipp,
persistent_data: &self.persistent_data, persistent_data: &self.persistent_data,
executor: &mut self.executor,
current_tool, current_tool,
preferences, preferences,
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.), device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
@@ -143,7 +147,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_id, document_id,
ipp, ipp,
persistent_data: &self.persistent_data, persistent_data: &self.persistent_data,
executor: &mut self.executor,
current_tool, current_tool,
preferences, preferences,
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.), device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
@@ -331,25 +334,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
data, data,
} => { } => {
let font = Font::new(font_family, font_style); let font = Font::new(font_family, font_style);
let mut font_cache = self.persistent_data.font_cache.as_ref().clone();
self.persistent_data.font_cache.insert(font, preview_url, data); font_cache.insert(font, preview_url, data);
self.executor.update_font_cache(self.persistent_data.font_cache.clone()); self.persistent_data.font_cache = Arc::new(font_cache);
for document_id in self.document_ids.iter() { responses.add(PortfolioMessage::CompileActiveDocument);
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);
}
} }
PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),
PortfolioMessage::Import => { PortfolioMessage::Import => {
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages // 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); responses.add(FrontendMessage::TriggerImport);
@@ -448,7 +437,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_migration_upgrades(&mut document, reset_node_definitions_on_open); document_migration_upgrades(&mut document, reset_node_definitions_on_open);
// Ensure each node has the metadata for its inputs // 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_input_metadata(node_id, node, &path);
document.network_interface.validate_display_name_metadata(node_id, &path); document.network_interface.validate_display_name_metadata(node_id, &path);
document.network_interface.validate_output_names(node_id, node, &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() { for entry in self.copy_buffer[clipboard as usize].iter().rev() {
paste(entry, responses, &mut all_new_ids) paste(entry, responses, &mut all_new_ids)
} }
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids });
} }
PortfolioMessage::PasteSerializedData { data } => { PortfolioMessage::PasteSerializedData { data } => {
@@ -539,9 +529,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
layers.push(layer); layers.push(layer);
} }
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids });
responses.add(Message::StartBuffer); // responses.add(Message::StartBuffer);
responses.add(PortfolioMessage::CenterPastedLayers { layers }); 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 { PortfolioMessage::PasteImage {
@@ -674,12 +664,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
if create_document { 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 // 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 }); 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 // 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 // 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(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
} }
} }
@@ -706,12 +696,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
if create_document { 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 // 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 }); 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 // 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 // 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(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
} }
} }
@@ -758,7 +748,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(BroadcastEvent::ToolAbort); responses.add(BroadcastEvent::ToolAbort);
responses.add(BroadcastEvent::SelectionChanged); responses.add(BroadcastEvent::SelectionChanged);
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open }); responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });
if node_graph_open { if node_graph_open {
responses.add(NodeGraphMessage::UpdateGraphBarRight); responses.add(NodeGraphMessage::UpdateGraphBarRight);
@@ -777,14 +767,332 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(PropertiesPanelMessage::Clear); 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_name,
file_type, file_type,
animation_export_data,
scale_factor, scale_factor,
bounds, bounds,
transparent_background, transparent_background,
} => { } => {
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document"); 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 { let export_config = ExportConfig {
file_name, file_name,
file_type, file_type,
@@ -793,37 +1101,76 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
transparent_background, transparent_background,
..Default::default() ..Default::default()
}; };
let result = self.executor.submit_document_export(document, export_config);
if let Err(description) = result { self.executor.submit_node_graph_evaluation(
responses.add(DialogMessage::DisplayDialogError { context,
title: "Unable to export document".to_string(), Vec::new(),
description, None,
}); Some(ExportConfig {
} file_name,
} file_type,
PortfolioMessage::SubmitActiveGraphRender => { scale_factor,
if let Some(document_id) = self.active_document_id { bounds,
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false }); transparent_background,
} size: scaled_size,
} }),
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,
);
if let Err(description) = result { // if let Some((start, end, fps)) = animation_export_data {
responses.add(DialogMessage::DisplayDialogError { // let total_frames = ((start - end) * fps) as u32;
title: "Unable to update node graph".to_string(), // for frame_index in 0..total_frames {
description, // 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 => { PortfolioMessage::ToggleRulers => {
if let Some(document) = self.active_document_mut() { if let Some(document) = self.active_document_mut() {
document.rulers_visible = !document.rulers_visible; document.rulers_visible = !document.rulers_visible;
@@ -834,7 +1181,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
} }
PortfolioMessage::UpdateDocumentWidgets => { PortfolioMessage::UpdateDocumentWidgets => {
if let Some(document) = self.active_document() { 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 => { PortfolioMessage::UpdateOpenDocumentsList => {
@@ -853,10 +1200,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
.collect::<Vec<_>>(); .collect::<Vec<_>>();
responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents }); 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>"# /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. // 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(); .to_string();
responses.add(Message::EndBuffer { responses.add(Message::ProcessQueue((graphene_std::renderer::EvaluationMetadata::default(), Vec::new())));
render_metadata: graphene_std::renderer::RenderMetadata::default(),
});
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error }); responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
} }
result result
} }
/// Get the id of the node that should be used as the target for the spreadsheet // Returns an error if the data could not be introspected, returns None if the data type could not be rendered.
pub fn inspect_node_id(&self) -> Option<NodeId> { fn try_render_thumbnail(&self, protonode_input: &CompiledProtonodeInput) -> ThumbnailRenderResult {
// Spreadsheet not open, skipping let Ok(introspected_data) = self.introspected_input_data.get(protonode_input) else {
if !self.spreadsheet.spreadsheet_view_open { log::error!("Could not introspect node from input: {:?}", protonode_input);
return None; 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 render_params = RenderParams {
let selected_nodes = document.network_interface.selected_nodes().0; 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 // Render the thumbnail data into an SVG string
if selected_nodes.len() != 1 { let mut render = SvgRender::new();
return None; 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 graph_craft::document::AbsoluteInputConnector;
use crate::node_graph_executor::InspectResult; use graphene_std::uuid::CompiledProtonodeInput;
/// The spreadsheet UI allows for instance data to be previewed. /// The spreadsheet UI allows for instance data to be previewed.
#[impl_message(Message, PortfolioMessage, Spreadsheet)] #[impl_message(Message, PortfolioMessage, Spreadsheet)]
@@ -7,27 +7,26 @@ use crate::node_graph_executor::InspectResult;
pub enum SpreadsheetMessage { pub enum SpreadsheetMessage {
ToggleOpen, ToggleOpen,
UpdateLayout { UpdateLayout { inpect_input: InspectInputConnector },
#[serde(skip)]
inspect_result: InspectResult,
},
PushToInstancePath { PushToInstancePath { index: usize },
index: usize, TruncateInstancePath { len: usize },
},
TruncateInstancePath {
len: usize,
},
ViewVectorDataDomain { ViewVectorDataDomain { domain: VectorDataDomain },
domain: VectorDataDomain,
},
} }
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Eq, Clone, Copy, Default, Debug)]
pub enum VectorDataDomain { pub enum VectorDataDomain {
#[default] #[default]
Points, Points,
Segments, Segments,
Regions, 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 super::VectorDataDomain;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout}; 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::prelude::*;
use crate::messages::tool::tool_messages::tool_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::Color;
use graphene_std::Context; use graphene_std::Context;
use graphene_std::GraphicGroupTable; use graphene_std::GraphicGroupTable;
use graphene_std::instances::Instances; use graphene_std::instances::Instances;
use graphene_std::memo::IORecord; use graphene_std::memo::IORecord;
use graphene_std::raster::Image; use graphene_std::raster::Image;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement}; use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement};
use std::any::Any; use std::any::Any;
use std::sync::Arc; 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. /// The spreadsheet UI allows for instance data to be previewed.
#[derive(Default, Debug, Clone, ExtractField)] #[derive(Default, Debug, Clone, ExtractField)]
pub struct SpreadsheetMessageHandler { pub struct SpreadsheetMessageHandler {
/// Sets whether or not the spreadsheet is drawn. /// Sets whether or not the spreadsheet is drawn.
pub spreadsheet_view_open: bool, pub spreadsheet_view_open: bool,
inspect_node: Option<NodeId>, inspect_input: Option<InspectInputConnector>,
introspected_data: Option<Arc<dyn Any + Send + Sync>>, // 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>, instances_path: Vec<usize>,
viewing_vector_data_domain: VectorDataDomain, viewing_vector_data_domain: VectorDataDomain,
} }
#[message_handler_data] #[message_handler_data]
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler { impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData> for SpreadsheetMessageHandler {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _: ()) { fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, data: SpreadsheetMessageHandlerData) {
let {introspected_data} = data;
match message { match message {
SpreadsheetMessage::ToggleOpen => { SpreadsheetMessage::ToggleOpen => {
self.spreadsheet_view_open = !self.spreadsheet_view_open; self.spreadsheet_view_open = !self.spreadsheet_view_open;
// Run the graph to grab the data
if self.spreadsheet_view_open { 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 // Update checked UI state for open
responses.add(MenuBarMessage::SendLayout); responses.add(MenuBarMessage::SendLayout);
self.update_layout(responses); self.update_layout(responses);
} }
SpreadsheetMessage::UpdateLayout { mut inspect_result } => { // Queued on introspection request, runs on introspection response when the data has been sent back to the editor
self.inspect_node = Some(inspect_result.inspect_node); SpreadsheetMessage::UpdateLayout { inpect_input } => {
self.introspected_data = inspect_result.take_data(); self.inspect_input = Some(inpect_input);
self.update_layout(responses) self.update_layout(introspected_data, responses);
} }
SpreadsheetMessage::PushToInstancePath { index } => { SpreadsheetMessage::PushToInstancePath { index } => {
self.instances_path.push(index); self.instances_path.push(index);
self.update_layout(responses); self.update_layout(introspected_data, responses);
} }
SpreadsheetMessage::TruncateInstancePath { len } => { SpreadsheetMessage::TruncateInstancePath { len } => {
self.instances_path.truncate(len); self.instances_path.truncate(len);
self.update_layout(responses); self.update_layout(introspected_data, responses);
} }
SpreadsheetMessage::ViewVectorDataDomain { domain } => { SpreadsheetMessage::ViewVectorDataDomain { domain } => {
self.viewing_vector_data_domain = 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 { 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 { responses.add(FrontendMessage::UpdateSpreadsheetState {
node: self.inspect_node, // The node is sent when the data is available
node: None,
open: self.spreadsheet_view_open, open: self.spreadsheet_view_open,
}); });
if !self.spreadsheet_view_open { if !self.spreadsheet_view_open {
@@ -82,12 +94,20 @@ impl SpreadsheetMessageHandler {
breadcrumbs: Vec::new(), breadcrumbs: Vec::new(),
vector_data_domain: self.viewing_vector_data_domain, vector_data_domain: self.viewing_vector_data_domain,
}; };
let mut layout = self let mut layout = match self.inspect_input {
.introspected_data Some(inspect_input) => {
.as_ref() match introspected_data.get(&inspect_input.protonode_input){
.map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data)) Some(data) => {
.unwrap_or_else(|| Some(label("No data"))) match generate_layout(instrospected_data, &mut layout_data) {
.unwrap_or_else(|| label("Failed to downcast 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 { if layout_data.breadcrumbs.len() > 1 {
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs) let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
@@ -110,21 +130,15 @@ struct LayoutData<'a> {
vector_data_domain: VectorDataDomain, 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. // We simply try random types. TODO: better strategy.
#[allow(clippy::manual_map)] #[allow(clippy::manual_map)]
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, ArtboardGroupTable>>() { if let Some(io) = introspected_data.downcast_ref::<ArtboardGroupTable>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), ArtboardGroupTable>>() { } else if let Some(io) = introspected_data.downcast_ref::<VectorDataTable>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() { } else if let Some(io) = introspected_data.downcast_ref::<GraphicGroupTable>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.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))
} else { } else {
None None
} }
@@ -2,8 +2,7 @@ use graphene_std::text::FontCache;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct PersistentData { pub struct PersistentData {
pub font_cache: FontCache, pub font_cache: Arc<FontCache>,
pub use_vello: bool,
} }
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)] #[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) { if let Ok(deserialized_preferences) = serde_json::from_str::<PreferencesMessageHandler>(&preferences) {
*self = deserialized_preferences; *self = deserialized_preferences;
responses.add(PortfolioMessage::EditorPreferences);
responses.add(PortfolioMessage::UpdateVelloPreference);
responses.add(PreferencesMessage::ModifyLayout { responses.add(PreferencesMessage::ModifyLayout {
zoom_with_scroll: self.zoom_with_scroll, zoom_with_scroll: self.zoom_with_scroll,
}); });
@@ -70,8 +68,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
// Per-preference messages // Per-preference messages
PreferencesMessage::UseVello { use_vello } => { PreferencesMessage::UseVello { use_vello } => {
self.use_vello = use_vello; self.use_vello = use_vello;
responses.add(PortfolioMessage::UpdateVelloPreference); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(PortfolioMessage::EditorPreferences);
} }
PreferencesMessage::VectorMeshes { enabled } => { PreferencesMessage::VectorMeshes { enabled } => {
self.vector_meshes = enabled; self.vector_meshes = enabled;
@@ -204,6 +204,6 @@ impl NumberOfPointsDial {
input_connector: InputConnector::node(node_id, 1), input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false), 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_connector: InputConnector::node(node_id, radius_index),
input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false), 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::BlendMode;
use graphene_std::raster_types::{CPU, GPU, RasterDataTable}; use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::Gradient; use graphene_std::vector::style::Gradient;
use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType}; use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType};
use std::collections::VecDeque; use std::collections::VecDeque;
@@ -152,8 +153,8 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
parent: first_layer, parent: first_layer,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartBuffer); responses.add(Message::StartQueue);
responses.add(PenToolMessage::RecalculateLatestPointsPosition); responses.add(PenToolMessage::RecalculateLatestPointsPosition);
} }
@@ -78,7 +78,7 @@ impl Line {
input_connector: InputConnector::node(node_id, 2), input_connector: InputConnector::node(node_id, 2),
input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false), 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) { 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 // Create the new layer, wait for the render output to return its transform, and then create the rest of the layer
else { else {
new_brush_layer(document, responses); new_brush_layer(document, responses);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartBuffer); responses.add(Message::StartQueue);
responses.add(BrushToolMessage::DragStart); responses.add(BrushToolMessage::DragStart);
BrushToolFsmState::Ready BrushToolFsmState::Ready
} }
@@ -251,7 +251,7 @@ impl Fsm for FreehandToolFsmState {
let nodes = vec![(NodeId(0), node)]; let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses); 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.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses); tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
tool_data.layer = Some(layer); tool_data.layer = Some(layer);
@@ -1258,7 +1258,7 @@ impl PenToolData {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); 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 // 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) // It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky)
responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport }); responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport });
} }
@@ -2085,7 +2085,7 @@ impl Fsm for PenToolFsmState {
node_ids: vec![layer.to_node()], node_ids: vec![layer.to_node()],
delete_children: true, delete_children: true,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} else if (latest_points && tool_data.prior_segment_endpoint.is_none()) } 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) || (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()], node_ids: vec![layer.unwrap().to_node()],
delete_children: true, delete_children: true,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
@@ -517,7 +517,7 @@ impl SelectToolData {
} }
let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect(); let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
self.layers_dragging = new_dragging; self.layers_dragging = new_dragging;
} }
@@ -555,7 +555,7 @@ impl SelectToolData {
}) })
.collect(); .collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
self.layers_dragging = original; self.layers_dragging = original;
@@ -491,7 +491,7 @@ impl Fsm for ShapeToolFsmState {
input_connector: InputConnector::node(node_id, 1), input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::U32(n + 1), false), input: NodeInput::value(TaggedValue::U32(n + 1), false),
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
self self
@@ -520,7 +520,7 @@ impl Fsm for ShapeToolFsmState {
input_connector: InputConnector::node(node_id, 1), input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::U32((n - 1).max(3)), false), input: NodeInput::value(TaggedValue::U32((n - 1).max(3)), false),
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
self self
@@ -599,7 +599,7 @@ impl Fsm for ShapeToolFsmState {
let nodes = vec![(NodeId(0), node)]; let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); 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 { match tool_data.current_shape {
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => { 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_options.stroke.apply_stroke(tool_data.weight, layer, responses);
tool_data.current_layer = Some(layer); tool_data.current_layer = Some(layer);
responses.add(Message::StartBuffer); responses.add(Message::StartQueue);
SplineToolFsmState::Drawing SplineToolFsmState::Drawing
} }
@@ -298,7 +298,7 @@ impl TextToolData {
node_ids: vec![self.layer.to_node()], node_ids: vec![self.layer.to_node()],
delete_children: true, delete_children: true,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
TextToolFsmState::Ready 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_connector: InputConnector::node(graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(), 1),
input: NodeInput::value(TaggedValue::String("".to_string()), false), 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), parent: document.new_layer_parent(true),
insert_index: 0, insert_index: 0,
}); });
responses.add(Message::StartBuffer); responses.add(Message::StartQueue);
responses.add(GraphOperationMessage::FillSet { responses.add(GraphOperationMessage::FillSet {
layer: self.layer, layer: self.layer,
fill: if editing_text.color.is_some() { 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::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> { fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option<LayerNodeIdentifier> {
@@ -649,7 +649,7 @@ impl Fsm for TextToolFsmState {
skip_rerender: false, skip_rerender: false,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
// Auto-panning // Auto-panning
let messages = [ let messages = [
@@ -710,7 +710,7 @@ impl Fsm for TextToolFsmState {
transform_in: TransformIn::Viewport, transform_in: TransformIn::Viewport,
skip_rerender: false, skip_rerender: false,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
// Auto-panning // Auto-panning
let messages = [ 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_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), input: NodeInput::value(TaggedValue::String(tool_data.new_text.clone()), false),
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
TextToolFsmState::Ready TextToolFsmState::Ready
} else { } else {
@@ -291,7 +291,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
update_colinear_handles(&selected_layers, document, responses); update_colinear_handles(&selected_layers, document, responses);
responses.add(DocumentMessage::EndTransaction); responses.add(DocumentMessage::EndTransaction);
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(PortfolioMessage::CompileActiveDocument);
} }
if using_path_tool { if using_path_tool {
+263 -375
View File
@@ -1,71 +1,94 @@
use std::sync::Arc;
use crate::consts::FILE_SAVE_SUFFIX; use crate::consts::FILE_SAVE_SUFFIX;
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use glam::{DAffine2, DVec2, UVec2}; use dyn_any::DynAny;
use graph_craft::document::value::{RenderOutput, TaggedValue}; use glam::DAffine2;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, generate_uuid}; 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::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences; use graph_craft::wasm_application_io::{EditorCompilationMetadata, EditorEvaluationMetadata, EditorMetadata};
use graphene_std::application_io::TimingInformation; use graphene_std::application_io::{CompilationMetadata, TimingInformation};
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig}; use graphene_std::application_io::{EditorEvaluationMetadata, NodeGraphUpdateMessage};
use graphene_std::renderer::RenderSvgSegmentList; use graphene_std::memo::IntrospectMode;
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender}; use graphene_std::renderer::{EvaluationMetadata, format_transform_matrix};
use graphene_std::renderer::{RenderMetadata, format_transform_matrix}; use graphene_std::renderer::{RenderMetadata, RenderSvgSegmentList};
use graphene_std::renderer::{RenderParams, SvgRender};
use graphene_std::text::FontCache; 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::VectorData;
use graphene_std::vector::style::ViewMode; 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; mod runtime_io;
use interpreted_executor::dynamic_executor::{EditorContext, ResolvedDocumentNodeMetadata};
pub use runtime_io::NodeRuntimeIO; pub use runtime_io::NodeRuntimeIO;
mod runtime; mod runtime;
pub use runtime::*; pub use runtime::*;
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct ExecutionRequest { pub struct CompilationRequest {
execution_id: u64, pub network: NodeNetwork,
render_config: RenderConfig, // 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 { pub struct CompilationResponse {
result: Result<ResolvedDocumentNodeTypesDelta, String>, result: Result<CompilationMetadata, String>,
node_graph_errors: GraphErrors, 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 { pub enum NodeGraphUpdate {
ExecutionResponse(ExecutionResponse),
CompilationResponse(CompilationResponse), CompilationResponse(CompilationResponse),
NodeGraphUpdateMessage(NodeGraphUpdateMessage), EvaluationResponse(EvaluationResponse),
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct NodeGraphExecutor { pub struct NodeGraphExecutor {
runtime_io: NodeRuntimeIO, runtime_io: NodeRuntimeIO,
futures: HashMap<u64, ExecutionContext>, futures: HashMap<u64, EvaluationContext>,
node_graph_hash: u64,
old_inspect_node: Option<NodeId>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ExecutionContext { struct EvaluationContext {
export_config: Option<ExportConfig>, export_config: Option<ExportConfig>,
} }
impl Default for NodeGraphExecutor {
fn default() -> Self {
Self {
futures: Default::default(),
runtime_io: NodeRuntimeIO::new(),
}
}
}
impl NodeGraphExecutor { impl NodeGraphExecutor {
/// A local runtime is useful on threads since having global state causes flakes /// A local runtime is useful on threads since having global state causes flakes
#[cfg(test)] #[cfg(test)]
@@ -75,189 +98,54 @@ impl NodeGraphExecutor {
let node_runtime = NodeRuntime::new(request_receiver, response_sender); let node_runtime = NodeRuntime::new(request_receiver, response_sender);
let node_executor = Self { let node_executor = Self {
futures: Default::default(), futures: HashMap::new(),
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver), runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
node_graph_hash: 0,
old_inspect_node: None,
}; };
(node_runtime, node_executor) (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. /// Updates the network to monitor all inputs. Useful for the testing.
#[cfg(test)] #[cfg(test)]
pub(crate) fn update_node_graph_instrumented(&mut self, document: &mut DocumentMessageHandler) -> Result<Instrumented, String> { 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 mut network = document.network_interface.document_network().clone();
let instrumented = Instrumented::new(&mut network); let instrumented = Instrumented::new(&mut network);
self.runtime_io self.runtime_io
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node: None })) .send(GraphRuntimeRequest::CompilationRequest(CompilationRequest { network, ..Default::default() }))
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
Ok(instrumented) Ok(instrumented)
} }
/// Update the cached network if necessary. /// Compile the network
fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, inspect_node: Option<NodeId>, ignore_hash: bool) -> Result<(), String> { pub fn submit_node_graph_compilation(&mut self, compilation_request: CompilationRequest) {
let network_hash = document.network_interface.document_network().current_hash(); self.runtime_io.send(GraphRuntimeRequest::CompilationRequest(compilation_request)).map_err(|e| e.to_string());
// 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(())
} }
/// Adds an evaluate request for whatever current network is cached. /// 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( pub fn submit_node_graph_evaluation(
&mut self, &mut self,
document: &mut DocumentMessageHandler, context: EditorContext,
viewport_resolution: UVec2, inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>,
time: TimingInformation, custom_node_to_evaluate: Option<SNI>,
inspect_node: Option<NodeId>, export_config: Option<ExportConfig>,
ignore_hash: bool, ) {
) -> Result<(), String> { let evaluation_id = generate_uuid();
self.update_node_graph(document, inspect_node, ignore_hash)?; self.runtime_io.send(GraphRuntimeRequest::EvaluationRequest(editor_evaluation_request)).map_err(|e| e.to_string());
self.submit_current_node_graph_evaluation(document, viewport_resolution, time)?; let evaluation_context = EvaluationContext { export_config };
self.futures.insert(evaluation_id, evaluation_context);
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(())
} }
// 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> { 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<_>>(); // Moved into portfolio message handler, since this is where the introspected inputs are saved
for response in results { for response in self.runtime_io.receive() {
match response { match response {
NodeGraphUpdate::ExecutionResponse(execution_response) => { NodeGraphUpdate::EvaluationResponse(EvaluationResponse {
let ExecutionResponse { evaluation_id,
execution_id, result,
result, transform,
responses: existing_responses, introspected_inputs,
transform, }) => {
vector_modify,
inspect_result,
} = execution_response;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
let node_graph_output = match result { let node_graph_output = match result {
@@ -269,51 +157,62 @@ impl NodeGraphExecutor {
return Err(format!("Node graph evaluation failed:\n{e}")); return Err(format!("Node graph evaluation failed:\n{e}"));
} }
}; };
let render_output = match node_graph_output {
responses.extend(existing_responses.into_iter().map(Into::into)); TaggedValue::RenderOutput(render_output) => render_output,
document.network_interface.update_vector_modify(vector_modify); value => {
return Err("Incorrect render type for exporting (expected NetworkOutput)".to_string());
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 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) => { NodeGraphUpdate::CompilationResponse(compilation_response) => {
let CompilationResponse { node_graph_errors, result } = execution_response; let CompilationResponse { node_graph_errors, result } = compilation_response;
let type_delta = match result { let compilation_metadata = match result {
Err(e) => { Err(e) => {
// Clear the click targets while the graph is in an un-renderable state // 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_click_targets(HashMap::new());
document.network_interface.update_vector_modify(HashMap::new()); document.network_interface.update_vector_modify(HashMap::new());
log::trace!("{e}"); document.node_graph_handler.node_graph_errors = node_graph_errors;
responses.add(NodeGraphMessage::UpdateTypes {
resolved_types: Default::default(),
node_graph_errors,
});
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
log::trace!("{e}");
return Err(format!("Node graph evaluation failed:\n{e}")); return Err(format!("Node graph evaluation failed:\n{e}"));
} }
Ok(result) => result, Ok(result) => result,
}; };
responses.add(PortfolioMessage::ProcessCompilationResponse { compilation_metadata });
responses.add(NodeGraphMessage::UpdateTypes {
resolved_types: type_delta,
node_graph_errors,
});
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
} }
@@ -321,31 +220,13 @@ impl NodeGraphExecutor {
Ok(()) Ok(())
} }
fn debug_render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque<Message>) { fn process_node_graph_output(
// Setup rendering &mut self,
let mut render = SvgRender::new(); node_graph_output: TaggedValue,
let render_params = RenderParams { introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
view_mode: ViewMode::Normal, transform: DAffine2,
culling_bounds: None, responses: &mut VecDeque<Message>,
thumbnail: false, ) -> Result<(), String> {
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> {
let mut render_output_metadata = RenderMetadata::default(); let mut render_output_metadata = RenderMetadata::default();
match node_graph_output { match node_graph_output {
TaggedValue::RenderOutput(render_output) => { TaggedValue::RenderOutput(render_output) => {
@@ -370,149 +251,156 @@ impl NodeGraphExecutor {
render_output_metadata = render_output.metadata; render_output_metadata = render_output.metadata;
} }
TaggedValue::Bool(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::String(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::F64(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::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::OptionalColor(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::VectorData(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::GraphicGroup(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::RasterData(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses), // TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
_ => { _ => {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}")); return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
} }
}; };
responses.add(Message::EndBuffer { responses.add(Message::ProcessQueue((render_output_metadata, introspected_inputs)));
render_metadata: render_output_metadata,
});
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
Ok(()) Ok(())
} }
} }
// pub enum AnimationState {
// #[default]
// Stopped,
// Playing {
// start: f64,
// },
// Paused {
// start: f64,
// pause_time: f64,
// },
// }
// Re-export for usage by tests in other modules // Re-export for usage by tests in other modules
#[cfg(test)] // #[cfg(test)]
pub use test::Instrumented; // pub use test::Instrumented;
#[cfg(test)] // #[cfg(test)]
mod test { // mod test {
use std::sync::Arc; // use std::sync::Arc;
use super::*; // use super::*;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; // use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::test_utils::test_prelude::{self, NodeGraphLayer}; // use crate::test_utils::test_prelude::{self, NodeGraphLayer};
use graph_craft::ProtoNodeIdentifier; // use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::NodeNetwork; // use graph_craft::document::NodeNetwork;
use graphene_std::Context; // use graphene_std::Context;
use graphene_std::NodeInputDecleration; // use graphene_std::NodeInputDecleration;
use graphene_std::memo::IORecord; // use graphene_std::memo::IORecord;
use test_prelude::LayerNodeIdentifier; // use test_prelude::LayerNodeIdentifier;
/// Stores all of the monitor nodes that have been attached to a graph // /// Stores all of the monitor nodes that have been attached to a graph
#[derive(Default)] // #[derive(Default)]
pub struct Instrumented { // pub struct Instrumented {
protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>, // protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,
protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>, // protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
} // }
impl Instrumented { // impl Instrumented {
/// Adds montior nodes to the network // /// Adds montior nodes to the network
fn add(&mut self, network: &mut NodeNetwork, path: &mut Vec<NodeId>) { // fn add(&mut self, network: &mut NodeNetwork, path: &mut Vec<NodeId>) {
// Required to do seperately to satiate the borrow checker. // // Required to do seperately to satiate the borrow checker.
let mut monitor_nodes = Vec::new(); // let mut monitor_nodes = Vec::new();
for (id, node) in network.nodes.iter_mut() { // for (id, node) in network.nodes.iter_mut() {
// Recursively instrument // // Recursively instrument
if let DocumentNodeImplementation::Network(nested) = &mut node.implementation { // if let DocumentNodeImplementation::Network(nested) = &mut node.implementation {
path.push(*id); // path.push(*id);
self.add(nested, path); // self.add(nested, path);
path.pop(); // path.pop();
} // }
let mut monitor_node_ids = Vec::with_capacity(node.inputs.len()); // let mut monitor_node_ids = Vec::with_capacity(node.inputs.len());
for input in &mut node.inputs { // for input in &mut node.inputs {
let node_id = NodeId::new(); // let node_id = NodeId::new();
let old_input = std::mem::replace(input, NodeInput::node(node_id, 0)); // let old_input = std::mem::replace(input, NodeInput::node(node_id, 0));
monitor_nodes.push((old_input, node_id)); // monitor_nodes.push((old_input, node_id));
path.push(node_id); // path.push(node_id);
monitor_node_ids.push(path.clone()); // monitor_node_ids.push(path.clone());
path.pop(); // path.pop();
} // }
if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation { // if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
path.push(*id); // path.push(*id);
self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone()); // self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());
self.protonodes_by_path.insert(path.clone(), monitor_node_ids); // self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
path.pop(); // path.pop();
} // }
} // }
for (input, monitor_id) in monitor_nodes { // for (input, monitor_id) in monitor_nodes {
let monitor_node = DocumentNode { // let monitor_node = DocumentNode {
inputs: vec![input], // inputs: vec![input],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), // implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)), // manual_composition: Some(graph_craft::generic!(T)),
skip_deduplication: true, // skip_deduplication: true,
..Default::default() // ..Default::default()
}; // };
network.nodes.insert(monitor_id, monitor_node); // network.nodes.insert(monitor_id, monitor_node);
} // }
} // }
/// Instrument a graph and return a new [Instrumented] state. // /// Instrument a graph and return a new [Instrumented] state.
pub fn new(network: &mut NodeNetwork) -> Self { // pub fn new(network: &mut NodeNetwork) -> Self {
let mut instrumented = Self::default(); // let mut instrumented = Self::default();
instrumented.add(network, &mut Vec::new()); // instrumented.add(network, &mut Vec::new());
instrumented // instrumented
} // }
fn downcast<Input: NodeInputDecleration>(dynamic: Arc<dyn std::any::Any + Send + Sync>) -> Option<Input::Result> // fn downcast<Input: NodeInputDecleration>(dynamic: Arc<dyn std::any::Any + Send + Sync>) -> Option<Input::Result>
where // where
Input::Result: Send + Sync + Clone + 'static, // Input::Result: Send + Sync + Clone + 'static,
{ // {
// This is quite inflexible since it only allows the footprint as inputs. // // This is quite inflexible since it only allows the footprint as inputs.
if let Some(x) = dynamic.downcast_ref::<IORecord<(), Input::Result>>() { // if let Some(x) = dynamic.downcast_ref::<IORecord<(), Input::Result>>() {
Some(x.output.clone()) // Some(x.output.clone())
} else if let Some(x) = dynamic.downcast_ref::<IORecord<Footprint, Input::Result>>() { // } else if let Some(x) = dynamic.downcast_ref::<IORecord<Footprint, Input::Result>>() {
Some(x.output.clone()) // Some(x.output.clone())
} else if let Some(x) = dynamic.downcast_ref::<IORecord<Context, Input::Result>>() { // } else if let Some(x) = dynamic.downcast_ref::<IORecord<Context, Input::Result>>() {
Some(x.output.clone()) // Some(x.output.clone())
} else { // } else {
panic!("cannot downcast type for introspection"); // panic!("cannot downcast type for introspection");
} // }
} // }
/// Grab all of the values of the input every time it occurs in the graph. // /// 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 // pub fn grab_all_input<'a, Input: NodeInputDecleration + 'a>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator<Item = Input::Result> + 'a
where // where
Input::Result: Send + Sync + Clone + 'static, // Input::Result: Send + Sync + Clone + 'static,
{ // {
self.protonodes_by_name // self.protonodes_by_name
.get(&Input::identifier()) // .get(&Input::identifier())
.map_or([].as_slice(), |x| x.as_slice()) // .map_or([].as_slice(), |x| x.as_slice())
.iter() // .iter()
.filter_map(|inputs| inputs.get(Input::INDEX)) // .filter_map(|inputs| inputs.get(Input::INDEX))
.filter_map(|input_monitor_node| runtime.executor.introspect(input_monitor_node).ok()) // .filter_map(|input_monitor_node| runtime.executor.introspect(input_monitor_node).ok())
.filter_map(Instrumented::downcast::<Input>) // .filter_map(Instrumented::downcast::<Input>)
} // }
pub fn grab_protonode_input<Input: NodeInputDecleration>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Input::Result> // pub fn grab_protonode_input<Input: NodeInputDecleration>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Input::Result>
where // where
Input::Result: Send + Sync + Clone + 'static, // Input::Result: Send + Sync + Clone + 'static,
{ // {
let input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?; // 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> // pub fn grab_input_from_layer<Input: NodeInputDecleration>(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, runtime: &NodeRuntime) -> Option<Input::Result>
where // where
Input::Result: Send + Sync + Clone + 'static, // Input::Result: Send + Sync + Clone + 'static,
{ // {
let node_graph_layer = NodeGraphLayer::new(layer, network_interface); // let node_graph_layer = NodeGraphLayer::new(layer, network_interface);
let node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?; // let node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?;
self.grab_protonode_input::<Input>(&vec![node], runtime) // 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::{GraphicElementRendered, RenderParams, SvgRender};
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment}; use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId};
use graphene_std::vector::style::ViewMode; use graphene_std::vector::style::ViewMode;
use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
@@ -24,8 +25,9 @@ use spin::Mutex;
use std::sync::Arc; use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
/// Persistent data between graph executions. It's updated via message passing from the editor thread with [`GraphRuntimeRequest`]`. /// Persistent data between graph evaluations. 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. /// [`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. /// 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 { pub struct NodeRuntime {
#[cfg(test)] #[cfg(test)]
@@ -33,14 +35,11 @@ pub struct NodeRuntime {
#[cfg(not(test))] #[cfg(not(test))]
executor: DynamicExecutor, executor: DynamicExecutor,
receiver: Receiver<GraphRuntimeRequest>, receiver: Receiver<GraphRuntimeRequest>,
sender: InternalNodeGraphUpdateSender, sender: NodeGraphRuntimeSender,
editor_preferences: EditorPreferences,
old_graph: Option<NodeNetwork>, application_io: Option<Arc<WasmApplicationIo>>,
update_thumbnails: bool,
editor_api: Arc<WasmEditorApi>,
node_graph_errors: GraphErrors, 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 /// Which node is inspected and which monitor node is used (if any) for the current execution
inspect_state: Option<InspectState>, inspect_state: Option<InspectState>,
@@ -48,26 +47,24 @@ pub struct NodeRuntime {
/// Mapping of the fully-qualified node paths to their preprocessor substitutions. /// Mapping of the fully-qualified node paths to their preprocessor substitutions.
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>, substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
// TODO: Remove, it doesn't need to be persisted anymore /// Stored in order to check for changes before sending to the frontend.
/// The current renders of the thumbnails for layer nodes. thumbnail_render_tagged_values: HashMap<CompiledProtonodeInput, TaggedValue>,
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
vector_modify: HashMap<NodeId, VectorData>,
} }
/// Messages passed from the editor thread to the node runtime thread. /// Messages passed from the editor thread to the node runtime thread.
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum GraphRuntimeRequest { pub enum GraphRuntimeRequest {
GraphUpdate(GraphUpdate), CompilationRequest(CompilationRequest),
ExecutionRequest(ExecutionRequest), // Makes a request to evaluate the network and stores data for the list of output connectors
FontCacheUpdate(FontCache), // Should only monitor data for nodes which need their thumbnails.
EditorPreferencesUpdate(EditorPreferences), 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
#[derive(Debug, serde::Serialize, serde::Deserialize)] // This is performed separately from execution requests, since thumbnails for animation should be updated once every 50ms or so.
pub struct GraphUpdate { ThumbnailRenderRequest(HashSet<CompiledProtonodeInput>),
pub(super) network: NodeNetwork, // 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.
/// The node that should be temporary inspected during execution // Can also be used by the spreadsheet/introspection system
pub(super) inspect_node: Option<NodeId>, IntrospectionRequest(HashSet<(CompiledProtonodeInput, IntrospectMode)>),
} }
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -81,21 +78,14 @@ pub struct ExportConfig {
} }
#[derive(Clone)] #[derive(Clone)]
struct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>); struct NodeGraphRuntimeSender(Sender<NodeGraphUpdate>);
impl InternalNodeGraphUpdateSender { impl NodeGraphRuntimeSender {
fn send_generation_response(&self, response: CompilationResponse) { fn send_compilation_response(&self, response: CompilationResponse) {
self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send response") self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send compilation response")
} }
fn send_evaluation_response(&self, response: EvaluationResponse) {
fn send_execution_response(&self, response: ExecutionResponse) { self.0.send(NodeGraphUpdate::EvaluationResponse(response)).expect("Failed to send evaluation response")
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")
} }
} }
@@ -106,151 +96,135 @@ impl NodeRuntime {
Self { Self {
executor: DynamicExecutor::default(), executor: DynamicExecutor::default(),
receiver, receiver,
sender: InternalNodeGraphUpdateSender(sender.clone()), sender: NodeGraphRuntimeSender(sender.clone()),
editor_preferences: EditorPreferences::default(),
old_graph: None,
update_thumbnails: true,
editor_api: WasmEditorApi { application_io: None,
font_cache: FontCache::default(),
editor_preferences: Box::new(EditorPreferences::default()),
node_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)),
application_io: None,
}
.into(),
node_graph_errors: Vec::new(), node_graph_errors: Vec::new(),
monitor_nodes: Vec::new(),
substitutions: preprocessor::generate_node_substitutions(), substitutions: preprocessor::generate_node_substitutions(),
thumbnail_render_tagged_values: HashSet::new(),
thumbnail_renders: Default::default(),
vector_modify: Default::default(),
inspect_state: None, inspect_state: None,
} }
} }
pub async fn run(&mut self) { pub async fn run(&mut self) {
if self.editor_api.application_io.is_none() { if self.application_io.is_none() {
self.editor_api = WasmEditorApi { #[cfg(not(test))]
#[cfg(not(test))] self.application_io = Some(Arc::new(WasmApplicationIo::new().await));
application_io: Some(WasmApplicationIo::new().await.into()), #[cfg(test)]
#[cfg(test)] self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await));
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();
} }
let mut font = None; // TODO: This deduplication of messages will probably cause more issues than it solved
let mut preferences = None; // let mut graph = None;
let mut graph = None; // let mut execution = 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() { for request in self.receiver.try_iter() {
match request { match request {
GraphRuntimeRequest::GraphUpdate(_) => graph = Some(request), GraphRuntimeRequest::CompilationRequest(CompilationRequest {
GraphRuntimeRequest::ExecutionRequest(_) => execution = Some(request), mut network,
GraphRuntimeRequest::FontCacheUpdate(_) => font = Some(request), font_cache,
GraphRuntimeRequest::EditorPreferencesUpdate(_) => preferences = Some(request), editor_metadata,
} }) => {
}
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 }) => {
// Insert the monitor node to manage the inspection // 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(); self.node_graph_errors.clear();
let result = self.update_network(network).await; let result = self.update_network(network).await;
self.update_thumbnails = true; self.sender.send_compilation_response(CompilationResponse {
self.sender.send_generation_response(CompilationResponse {
result, result,
node_graph_errors: self.node_graph_errors.clone(), node_graph_errors: self.node_graph_errors.clone(),
}); });
} }
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => { GraphRuntimeRequest::EvaluationRequest(EvaluationRequest {
let transform = render_config.viewport.transform; 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 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 introspected_inputs = Vec::new();
let inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor)); 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 { self.sender.send_evaluation_response(EvaluationResponse {
execution_id, evaluation_id,
result, result,
responses,
transform, transform,
vector_modify: self.vector_modify.clone(), introspected_inputs,
inspect_result,
}); });
} }
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); preprocessor::expand_network(&mut graph, &self.substitutions);
// Creates a network where the node paths to the document network are prefixed with NodeId(0)
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone()); let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
// We assume only one output // We assume only one output
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled"); assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {}; // Modifies the NodeNetwork so the tagged values are removed and the document nodes with protonode implementations have their protonode ids set
let proto_network = match c.compile_single(scoped_network) { // 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, 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?"); assert_ne!(proto_network.len(), 0, "No proto nodes exist?");
self.executor.update(proto_network).await.map_err(|e| { let result = match self.executor.update(proto_network).await {
self.node_graph_errors.clone_from(&e); Ok((types_to_add, types_to_remove)) => {
format!("{e:?}") // 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> { async fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {
@@ -269,117 +243,6 @@ impl NodeRuntime {
Ok(result) 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 { 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(); let mut node_runtime = NODE_RUNTIME.lock();
node_runtime.replace(runtime) 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[]; readonly nodeTypes!: FrontendNodeType[];
} }
export class UpdateNodeThumbnail extends JsMessage { export class UpdateThumbnails extends JsMessage {
readonly id!: bigint; readonly add!: [bigint, string][];
readonly value!: string; readonly clear!: bigint[];
} }
export class UpdateNodeGraphSelection extends JsMessage { export class UpdateNodeGraphSelection extends JsMessage {
@@ -1683,7 +1683,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateNodeGraphTransform, UpdateNodeGraphTransform,
UpdateNodeGraphControlBarLayout, UpdateNodeGraphControlBarLayout,
UpdateNodeGraphSelection, UpdateNodeGraphSelection,
UpdateNodeThumbnail, UpdateThumbnails: UpdateThumbnail,
UpdateOpenDocumentsList, UpdateOpenDocumentsList,
UpdatePropertyPanelSectionsLayout, UpdatePropertyPanelSectionsLayout,
UpdateSpreadsheetLayout, UpdateSpreadsheetLayout,
+8 -3
View File
@@ -24,7 +24,7 @@ import {
UpdateNodeGraphWires, UpdateNodeGraphWires,
UpdateNodeGraphSelection, UpdateNodeGraphSelection,
UpdateNodeGraphTransform, UpdateNodeGraphTransform,
UpdateNodeThumbnail, UpdateThumbnails,
UpdateWirePathInProgress, UpdateWirePathInProgress,
} from "@graphite/messages"; } from "@graphite/messages";
@@ -168,9 +168,14 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateNodeThumbnail, (updateNodeThumbnail) => { editor.subscriptions.subscribeJsMessage(UpdateThumbnails, (updateThumbnail) => {
update((state) => { 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; return state;
}); });
}); });
+1
View File
@@ -234,6 +234,7 @@ impl CloneVarArgs for Arc<OwnedContextImpl> {
} }
} }
// Lifetime isnt necessary?
pub type Context<'a> = Option<Arc<OwnedContextImpl>>; pub type Context<'a> = Option<Arc<OwnedContextImpl>>;
type DynRef<'a> = &'a (dyn Any + Send + Sync); type DynRef<'a> = &'a (dyn Any + Send + Sync);
type DynBox = Box<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 ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
pub use memo::IntrospectMode;
pub use memo::MemoHash; pub use memo::MemoHash;
pub use num_traits; pub use num_traits;
pub use raster::Color; pub use raster::Color;
@@ -58,11 +59,18 @@ pub trait Node<'i, Input> {
fn node_name(&self) -> &'static str { fn node_name(&self) -> &'static str {
std::any::type_name::<Self>() 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>> { /// Get the call argument or output data for the monitor node on the next evaluation after set_introspect_input
log::warn!("Node::serialize not implemented for {}", std::any::type_name::<Self>()); /// 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 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; 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"); 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(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug)] pub enum IntrospectMode {
pub struct IORecord<I, O> { Input,
pub input: I, Data,
pub output: O,
} }
/// Caches the output of the last graph evaluation for introspection /// Caches the output of the last graph evaluation for introspection
#[derive(Default)] #[derive(Default)]
pub struct MonitorNode<I, T, N> { pub struct MonitorNode<I, O, N> {
#[allow(clippy::type_complexity)] #[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, 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 where
I: Clone + 'static + Send + Sync, I: Clone + 'static + Send + Sync,
T: Clone + 'static + Send + Sync, O: Clone + 'static + Send + Sync,
for<'a> N: Node<'a, I, Output: Future<Output = T> + WasmNotSend> + 'i, 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 { 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 { Box::pin(async move {
let output = output_fut.await; let output = self.node.eval(input.clone()).await;
*io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() })); 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 output
}) })
} }
fn serialize(&self) -> Option<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.
let io = self.io.lock().unwrap(); fn introspect(&self, introspect_mode: IntrospectMode) -> Option<Box<dyn std::any::Any + Send + Sync>> {
(io).as_ref().map(|output| output.clone() as Arc<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> { impl<I, O, N> MonitorNode<I, O, N> {
pub fn new(node: N) -> MonitorNode<I, T, N> { pub fn new(node: N) -> MonitorNode<I, O, N> {
MonitorNode { io: Arc::new(Mutex::new(None)), node } 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) { fn reset(&self) {
self.0.reset(); 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> { 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 { 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 SharedNodeContainer = std::sync::Arc<NodeContainer>;
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>; pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
pub type MonitorConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>;
#[derive(Clone)] #[derive(Clone)]
pub struct NodeContainer { pub struct NodeContainer {
@@ -208,11 +209,10 @@ where
#[inline] #[inline]
fn eval(&'input self, input: I) -> Self::Output { fn eval(&'input self, input: I) -> Self::Output {
{ {
let node_name = self.node.node_name();
let input = Box::new(input); let input = Box::new(input);
let future = self.node.eval(input); let future = self.node.eval(input);
Box::pin(async move { 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 *out
}) })
} }
@@ -220,11 +220,8 @@ where
fn reset(&self) { fn reset(&self) {
self.node.reset(); 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> { impl<I, O> DowncastBothNode<I, O> {
pub const fn new(node: SharedNodeContainer) -> Self { pub const fn new(node: SharedNodeContainer) -> Self {
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> { pub struct FutureWrapperNode<Node> {
node: Node, node: Node,
} }
@@ -252,11 +254,6 @@ where
fn reset(&self) { fn reset(&self) {
self.node.reset(); 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> { impl<N> FutureWrapperNode<N> {
@@ -294,10 +291,6 @@ where
fn reset(&self) { fn reset(&self) {
self.node.reset(); 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> impl<'input, I, O, N> DynAnyNode<I, O, N>
where where
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::Node; use crate::registry::Node;
use std::marker::PhantomData; use std::marker::PhantomData;
/// This is how we can generically define composition of two nodes. /// 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) 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))), _ => 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> { pub fn from_type(input: &Type) -> Option<Self> {
match input { match input {
Type::Generic(_) => None, Type::Generic(_) => None,
@@ -372,6 +382,18 @@ impl TaggedValue {
_ => panic!("Passed value is not of type u32"), _ => 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 { 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::document::NodeNetwork;
use crate::graphene_compiler::Compiler; use crate::graphene_compiler::Compiler;
use crate::proto::ProtoNetwork;
pub fn load_network(document_string: &str) -> NodeNetwork { pub fn load_network(document_string: &str) -> NodeNetwork {
let document: serde_json::Value = serde_json::from_str(document_string).expect("Failed to parse document"); 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") 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 { 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::fs::read(format!("../../demo-artwork/{name}.graphite")).expect("failed to read file");
let content = std::str::from_utf8(&content).unwrap(); 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 futures::executor::block_on;
use graph_craft::document::*; use graph_craft::document::*;
use graph_craft::graphene_compiler::{Compiler, Executor}; 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::util::load_network;
use graph_craft::wasm_application_io::EditorPreferences; use graph_craft::wasm_application_io::EditorPreferences;
use graphene_core::text::FontCache; 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); let mut network = load_network(&document_string);
fix_nodes(&mut network); fix_nodes(&mut network);
let substitutions = preprocessor::generate_node_substitutions(); let substitutions = preprocessor::generate_node_substitutions();
preprocessor::expand_network(&mut network, &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 {}; 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>> { fn create_executor(proto_network: ProtoNetwork) -> Result<DynamicExecutor, Box<dyn Error>> {
@@ -2,13 +2,13 @@ use criterion::BenchmarkGroup;
use criterion::measurement::Measurement; use criterion::measurement::Measurement;
use futures::executor::block_on; use futures::executor::block_on;
use graph_craft::proto::ProtoNetwork; 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; use interpreted_executor::dynamic_executor::DynamicExecutor;
pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) { pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {
let network = load_from_name(name); let mut network = load_from_name(name);
let proto_network = compile(network); let proto_network = network.flatten().unwrap();
let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap(); let executor = block_on(DynamicExecutor::new(proto_network.0)).unwrap();
(executor, proto_network) (executor, proto_network)
} }
@@ -7,8 +7,8 @@ use graphene_std::transform::Footprint;
use interpreted_executor::dynamic_executor::DynamicExecutor; use interpreted_executor::dynamic_executor::DynamicExecutor;
fn update_executor<M: Measurement>(name: &str, c: &mut BenchmarkGroup<M>) { fn update_executor<M: Measurement>(name: &str, c: &mut BenchmarkGroup<M>) {
let network = load_from_name(name); let mut network = load_from_name(name);
let proto_network = compile(network); let proto_network = network.flatten().unwrap().0;
let empty = ProtoNetwork::default(); let empty = ProtoNetwork::default();
let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); 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>) { fn run_once<M: Measurement>(name: &str, c: &mut BenchmarkGroup<M>) {
let network = load_from_name(name); let mut network = load_from_name(name);
let proto_network = compile(network); let proto_network = network.flatten().unwrap().0;
let executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap(); let executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap();
let footprint = Footprint::default(); 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 dyn_any::StaticType;
use graph_craft::Type; use glam::DAffine2;
use graph_craft::document::NodeId;
use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode}; use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode};
use graph_craft::graphene_compiler::Executor; use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, downcast_node};
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext};
use graph_craft::proto::{GraphErrorType, GraphErrors}; 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::collections::{HashMap, HashSet};
use std::error::Error; use std::error::Error;
use std::sync::Arc; 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 ...>`. /// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.
#[derive(Clone)] #[derive(Clone)]
pub struct DynamicExecutor { pub struct DynamicExecutor {
output: NodeId, output: Option<SNI>,
/// Stores all of the dynamic node structs. /// Stores all of the dynamic node structs.
tree: BorrowTree, tree: BorrowTree,
/// Stores the types of the proto nodes. /// Stores the types of the proto nodes.
typing_context: TypingContext, typing_context: TypingContext,
// This allows us to keep the nodes around for one more frame which is used for introspection // TODO: Add lifetime for removed nodes so that if a SNI changes, then changes back to its previous SNI, the node does
orphaned_nodes: HashSet<NodeId>, // not have to be reinserted
// lifetime: HashSet<(SNI, usize)>,
} }
impl Default for DynamicExecutor { impl Default for DynamicExecutor {
fn default() -> Self { fn default() -> Self {
Self { Self {
output: Default::default(), output: None,
tree: Default::default(), tree: Default::default(),
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY), typing_context: TypingContext::new(&NODE_REGISTRY, &MONITOR_NODES),
orphaned_nodes: HashSet::new(),
} }
} }
} }
#[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 { impl DynamicExecutor {
pub async fn new(proto_network: ProtoNetwork) -> Result<Self, GraphErrors> { pub async fn new(proto_network: Vec<ProtoNode>) -> Result<Self, GraphErrors> {
let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY); let mut typing_context = TypingContext::default();
typing_context.update(&proto_network)?; 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?; let tree = BorrowTree::new(proto_network, &typing_context).await?;
Ok(Self { Ok(Self { tree, output, typing_context })
tree,
output,
typing_context,
orphaned_nodes: HashSet::new(),
})
} }
/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible. /// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.
#[cfg_attr(debug_assertions, inline(never))] #[cfg_attr(debug_assertions, inline(never))]
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<ResolvedDocumentNodeTypesDelta, GraphErrors> { pub async fn update(mut self, proto_network: Vec<ProtoNode>) -> Result<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), GraphErrors> {
self.output = proto_network.output; self.output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
self.typing_context.update(&proto_network)?; self.typing_context.update(&proto_network)?;
let (add, orphaned) = self.tree.update(proto_network, &self.typing_context).await?; // A protonode id can change while having the same document path, and the path can change while having the same stable node id.
let old_to_remove = core::mem::replace(&mut self.orphaned_nodes, orphaned); // Either way, the mapping of paths to ids and ids to paths has to be kept in sync.
let mut remove = Vec::with_capacity(old_to_remove.len() - self.orphaned_nodes.len().min(old_to_remove.len())); // The mapping of monitor node paths has to kept in sync as well.
for node_id in old_to_remove { let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?;
if self.orphaned_nodes.contains(&node_id) { let mut remove = Vec::new();
let path = self.tree.free_node(node_id); for sni in orphaned_proto_nodes {
self.typing_context.remove_inference(node_id); let Some(types) = self.typing_context.type_of(sni) else {
if let Some(path) = path { log::error!("Could not get type for protonode {sni} when removing");
remove.push(path); 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. /// Intospect the value for that specific protonode input, returning for example the cached value for a monitor node.
pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> { pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result<Box<dyn std::any::Any + Send + Sync>, IntrospectError> {
self.tree.introspect(node_path) 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> { 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 { pub fn tree(&self) -> &BorrowTree {
&self.tree &self.tree
} }
pub fn output(&self) -> NodeId { pub fn output(&self) -> Option<SNI> {
self.output self.output
} }
pub fn output_type(&self) -> Option<Type> { 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 { pub fn execute<I>(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>>
nodes.flat_map(|id| self.tree.source_map().get(&id).map(|(_, b)| (id, b.clone()))) where
// TODO: https://github.com/GraphiteEditor/Graphite/issues/1767 I: dyn_any::StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
// 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>>> {
Box::pin(async move { Box::pin(async move {
use futures::FutureExt; 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; let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await;
match wrapped_result { 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)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IntrospectError { pub enum IntrospectError {
PathNotFound(Vec<NodeId>), PathNotFound(Vec<NodeId>),
ProtoNodeNotFound(NodeId), ProtoNodeNotFound(SNI),
NoData, NoData,
RuntimeNotReady, RuntimeNotReady,
IntrospectNotImplemented,
} }
impl std::fmt::Display for IntrospectError { 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::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {:?}", id),
IntrospectError::NoData => write!(f, "No data found for this node"), IntrospectError::NoData => write!(f, "No data found for this node"),
IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"), 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. /// A store of the dynamically typed nodes and also the source map.
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct BorrowTree { pub struct BorrowTree {
/// A hashmap of node IDs and dynamically typed nodes. // A hashmap of node IDs and dynamically typed nodes, as well as the number of inserted monitor nodes
nodes: HashMap<NodeId, (SharedNodeContainer, Path)>, nodes: HashMap<SNI, SharedNodeContainer>,
/// A hashmap from the document path to the proto node ID.
source_map: HashMap<Path, (NodeId, NodeTypes)>,
} }
impl BorrowTree { 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(); let mut nodes = BorrowTree::default();
for (id, node) in proto_network.nodes { for node in proto_network {
nodes.push_node(id, node, typing_context).await? nodes.push_node(node, typing_context).await?
} }
Ok(nodes) Ok(nodes)
} }
/// Pushes new nodes into the tree and return orphaned nodes /// Pushes new nodes into the tree and returns a vec of document nodes that had their types changed, and a vec of all nodes that were removed (including auto inserted value nodes)
pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<Path>, HashSet<NodeId>), GraphErrors> { pub async fn update(&mut self, proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<(Vec<SNI>, HashSet<SNI>), GraphErrors> {
let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect(); let mut old_nodes = self.nodes.keys().copied().into_iter().collect::<HashSet<_>>();
let mut new_nodes: Vec<_> = Vec::new(); // List of all document node paths that need to be updated, which occurs if their path changes or type changes
// 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. let mut nodes_with_new_type = Vec::new();
for (id, node) in proto_network.nodes { for node in proto_network {
if !self.nodes.contains_key(&id) { let sni = node.stable_node_id;
new_nodes.push(node.original_location.path.clone().unwrap_or_default().into()); old_nodes.remove(&sni);
self.push_node(id, node, typing_context).await?; let sni = node.stable_node_id;
} else if self.update_source_map(id, typing_context, &node) { if !self.nodes.contains_key(&sni) {
new_nodes.push(node.original_location.path.clone().unwrap_or_default().into()); 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> { fn node_deps(&self, nodes: &[SNI]) -> Vec<SharedNodeContainer> {
nodes.iter().map(|node| self.nodes.get(node).unwrap().0.clone()).collect() nodes.iter().map(|node| self.nodes.get(node).unwrap().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())
} }
/// Evaluate the output node of the [`BorrowTree`]. /// Evaluate the output node of the [`BorrowTree`].
@@ -235,18 +307,18 @@ impl BorrowTree {
I: StaticType + 'i + Send + Sync, I: StaticType + 'i + Send + Sync,
O: StaticType + 'i, 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)); let output = node.eval(Box::new(input));
dyn_any::downcast::<O>(output.await).ok().map(|o| *o) dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
} }
/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value. /// 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. /// 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 where
I: StaticType + 'static + Send + Sync, I: StaticType + 'static + Send + Sync,
{ {
let (node, _path) = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?; let inserted_node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
let output = node.eval(Box::new(input)); let output = inserted_node.eval(Box::new(input));
TaggedValue::try_from_any(output.await) TaggedValue::try_from_any(output.await)
} }
@@ -305,58 +377,12 @@ impl BorrowTree {
/// - Removes the node from `nodes` HashMap. /// - 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. /// - 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. /// - Returns `None` if the node is not found in the `nodes` HashMap.
pub fn free_node(&mut self, id: NodeId) -> Option<Path> { pub fn free_node(&mut self, id: &SNI, inputs: usize) {
let (_, path) = self.nodes.remove(&id)?; self.nodes.remove(&id);
if self.source_map.get(&path)?.0 == id { // Also remove all corresponding monitor nodes
self.source_map.remove(&path); for monitor_index in 1..=inputs {
return Some(path); 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`. /// 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. /// - `Nodes`: Constructs a node using other nodes as dependencies.
/// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments. /// - 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. /// - 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> { async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> {
self.update_source_map(id, typing_context, &proto_node); let sni = proto_node.stable_node_id;
let path = proto_node.original_location.path.clone().unwrap_or_default(); // Move the value into the upcast node instead of cloning it
match proto_node.construction_args {
match &proto_node.construction_args {
ConstructionArgs::Value(value) => { ConstructionArgs::Value(value) => {
let node = if let TaggedValue::EditorApi(api) = &**value { // The constructor for nodes with value construction args (value nodes) is not called.
let editor_api = UpcastAsRefNode::new(api.clone()); // It is not necessary to clone the Arc for the wasm editor api, since the value node is deduplicated and only called once.
let node = Box::new(editor_api) as TypeErasedBox<'_>; // It is cloned whenever it is evaluated
NodeContainer::new(node) let upcasted = UpcastNode::new(value);
} else { let node = Box::new(upcasted) as TypeErasedBox<'_>;
let upcasted = UpcastNode::new(value.to_owned()); self.nodes.insert(sni, NodeContainer::new(node));
let node = Box::new(upcasted) as TypeErasedBox<'_>;
NodeContainer::new(node)
};
self.store_node(node, id, path.into());
} }
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(ids) => { ConstructionArgs::Nodes(ref node_construction_args) => {
let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect(); let construction_nodes = self.node_deps(&node_construction_args.inputs);
let construction_nodes = self.node_deps(&ids);
let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?; let types = typing_context.type_of(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let node = constructor(construction_nodes).await; 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); let node = NodeContainer::new(node);
self.store_node(node, id, path.into()); self.nodes.insert(sni, node);
} }
}; };
Ok(()) Ok(())
} }
/// Returns the source map of the borrow tree
pub fn source_map(&self) -> &HashMap<Path, (NodeId, NodeTypes)> {
&self.source_map
}
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graphene_std::uuid::NodeId;
#[test] #[test]
fn push_node_sync() { fn push_node_sync() {
let mut tree = BorrowTree::default(); 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 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(); futures::executor::block_on(future).unwrap();
let _node = tree.get(NodeId(0)).unwrap(); let _node = tree.get(NodeId(0)).unwrap();
let result = futures::executor::block_on(tree.eval(NodeId(0), ())); 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; use graph_craft::graphene_compiler::Compiler;
let 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 dyn_any::StaticType;
use glam::{DVec2, IVec2, UVec2}; use glam::{DVec2, IVec2, UVec2};
use graph_craft::document::value::RenderOutput; 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::color::Color;
use graphene_core::raster::*; use graphene_core::raster::*;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; 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}; use graphene_std::application_io::{ImageTexture, SurfaceFrame};
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle}; 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 once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
#[cfg(feature = "gpu")] #[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()); 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 { mod node_registry_macros {
macro_rules! async_node { macro_rules! async_node {
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly) // 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| { |mut args| {
Box::pin(async move { Box::pin(async move {
args.reverse(); 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); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox 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 async_node;
pub(crate) use convert_node; pub(crate) use convert_node;
pub(crate) use into_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 graphene_std::uuid::NodeId;
use std::sync::Arc; 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 { pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEditorApi>) -> NodeNetwork {
network.generate_node_paths(&[]);
let inner_network = DocumentNode { let inner_network = DocumentNode {
implementation: DocumentNodeImplementation::Network(network), implementation: DocumentNodeImplementation::Network(network),
inputs: vec![], inputs: vec![],
..Default::default() ..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 { let render_node = DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::Network(NodeNetwork { 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 // wrap the inner network in a scope
let nodes = vec![ let nodes = vec![inner_network, render_node];
inner_network,
render_node,
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)],
..Default::default()
},
];
NodeNetwork { NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(), 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` // TODO(TrueDoctor): check if it makes sense to set `generated` to `true`
generated: false, generated: false,
} }
+1
View File
@@ -3,6 +3,7 @@ use graph_craft::document::*;
use graph_craft::proto::RegistryValueSource; use graph_craft::proto::RegistryValueSource;
use graph_craft::{ProtoNodeIdentifier, concrete}; use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::registry::*; use graphene_std::registry::*;
use graphene_std::uuid::NodeId;
use graphene_std::*; use graphene_std::*;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};