WIP: Thumbnails

This commit is contained in:
Adam
2025-07-16 01:43:00 -07:00
parent 8b665d158c
commit fcd379a62f
50 changed files with 1208 additions and 1053 deletions
+15 -25
View File
@@ -44,8 +44,11 @@ impl DispatcherMessageHandlers {
/// In addition, these messages do not change any state in the backend (aside from caches).
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::CompileActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::EvaluateActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::IntrospectActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::EvaluateActiveDocumentWithThumbnails),
// MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::IntrospectActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh,
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh,
))),
@@ -123,13 +126,13 @@ impl Dispatcher {
}
}
// Add all messages to the queue if queuing messages (except from the end queue message)
if !matches!(message, Message::EndIntrospectionQueue) {
if self.queueing_introspection_messages {
self.introspection_queue.push(message);
return;
}
}
// // Add all messages to the queue if queuing messages (except from the end queue message)
// if !matches!(message, Message::EndIntrospectionQueue) {
// if self.queueing_introspection_messages {
// self.introspection_queue.push(message);
// return;
// }
// }
// Print the message at a verbosity level of `info`
self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
@@ -144,9 +147,10 @@ impl Dispatcher {
Message::EndEvaluationQueue => {
self.queueing_evaluation_messages = false;
}
Message::ProcessEvaluationQueue(render_output_metadata) => {
Message::ProcessEvaluationQueue(render_output_metadata, introspected_nodes) => {
let update_message = PortfolioMessage::ProcessEvaluationResponse {
evaluation_metadata: render_output_metadata,
introspected_nodes,
}
.into();
// Update the state with the render output and introspected inputs
@@ -154,23 +158,9 @@ impl Dispatcher {
// Schedule all queued messages to be run, which use the introspected inputs (in the order they were added)
Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.evaluation_queue));
}
Message::StartIntrospectionQueue => {
self.queueing_introspection_messages = true;
}
Message::EndIntrospectionQueue => {
self.queueing_introspection_messages = false;
}
Message::ProcessIntrospectionQueue(introspection_response) => {
let update_message = PortfolioMessage::ProcessIntrospectionResponse { introspection_response }.into();
// Update the state with the render output and introspected inputs
Self::schedule_execution(&mut self.message_queues, true, [update_message]);
// Schedule all queued messages to be run, which use the introspected inputs (in the order they were added)
Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.introspection_queue));
// Clear all introspected data after the queued messages are execucted
let clear_message = PortfolioMessage::ClearIntrospectedData.into();
// Clear the introspected inputs since they are no longer required, and will cause a memory leak if not removed
Self::schedule_execution(&mut self.message_queues, true, [clear_message]);
}
Message::NoOp => {}
@@ -84,7 +84,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
}
AnimationMessage::SetFrameIndex { frame } => {
self.frame_index = frame;
responses.add(PortfolioMessage::EvaluateActiveDocument);
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
@@ -100,7 +100,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
}
AnimationMessage::UpdateTime => {
if self.is_playing() {
responses.add(PortfolioMessage::EvaluateActiveDocument);
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
if self.live_preview_recently_zero {
// Update the restart and pause/play buttons
@@ -116,7 +116,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
_ => AnimationState::Stopped,
};
self.live_preview_recently_zero = true;
responses.add(PortfolioMessage::EvaluateActiveDocument);
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
@@ -31,8 +31,8 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
}
responses.add(Message::StartEvaluationQueue);
responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(Message::EndEvaluationQueue);
responses.add(DocumentMessage::DeselectAllLayers);
responses.add(Message::EndEvaluationQueue);
}
}
@@ -1,15 +1,15 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeSNIUpdate, FrontendNodeType, Transform,
};
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate, WireSNIUpdate};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
use graphene_std::uuid::NodeId;
use graphene_std::raster::color::Color;
use graphene_std::text::Font;
use graphene_std::uuid::{NodeId, SNI};
#[impl_message(Message, Frontend)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -128,6 +128,10 @@ pub enum FrontendMessage {
#[serde(rename = "box")]
box_selection: Option<BoxSelection>,
},
UpdateContextDuringEvaluation {
#[serde(rename = "contextDuringEvaluation")]
context_during_evaluation: Vec<(SNI, usize, String)>,
},
UpdateContextMenuInformation {
#[serde(rename = "contextMenuInformation")]
context_menu_information: Option<ContextMenuInformation>,
@@ -224,6 +228,10 @@ pub enum FrontendMessage {
#[serde(rename = "setColorChoice")]
set_color_choice: Option<String>,
},
UpdateGraphBreadcrumbPath {
#[serde(rename = "breadcrumbPath")]
breadcrumb_path: Vec<NodeId>,
},
UpdateGraphFadeArtwork {
percentage: f64,
},
@@ -263,7 +271,7 @@ pub enum FrontendMessage {
UpdateNodeGraphWires {
wires: Vec<WirePathUpdate>,
},
ClearAllNodeGraphWires,
ClearAllNodeGraphWirePaths,
UpdateNodeGraphControlBarLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
@@ -287,7 +295,10 @@ pub enum FrontendMessage {
UpdateThumbnails {
add: Vec<(NodeId, String)>,
clear: Vec<NodeId>,
// remove: Vec<NodeId>,
#[serde(rename = "wireSNIUpdates")]
wire_sni_updates: Vec<WireSNIUpdate>,
#[serde(rename = "layerSNIUpdates")]
layer_sni_updates: Vec<FrontendNodeSNIUpdate>,
},
UpdateToolOptionsLayout {
#[serde(rename = "layoutTarget")]
+1 -6
View File
@@ -13,12 +13,7 @@ pub enum Message {
EndEvaluationQueue,
// Processes all messages that are queued to be run after evaluation, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete
#[serde(skip)]
ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata),
StartIntrospectionQueue,
EndIntrospectionQueue,
// Processes all messages that are queued to be run after introspection, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete
#[serde(skip)]
ProcessIntrospectionQueue(IntrospectionResponse),
ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata, IntrospectionResponse),
#[child]
Animation(AnimationMessage),
#[child]
@@ -442,7 +442,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::EnterNestedNetwork { node_id } => {
self.breadcrumb_network_path.push(node_id);
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
@@ -472,7 +472,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.breadcrumb_network_path.pop();
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
}
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
@@ -539,7 +539,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars);
if opened {
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::UnloadWirePaths);
}
if open {
responses.add(ToolMessage::DeactivateTools);
@@ -1424,7 +1424,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
center: Key::Alt,
duplicate: Key::Alt,
}));
responses.add(PortfolioMessage::EvaluateActiveDocument);
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
} else {
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
return;
@@ -1440,7 +1440,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(NodeGraphMessage::UpdateEdges);
responses.add(NodeGraphMessage::UpdateBoxSelection);
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(NodeGraphMessage::UpdateVisibleNodes);
responses.add(NodeGraphMessage::SendWirePaths);
responses.add(FrontendMessage::UpdateNodeGraphTransform {
transform: Transform {
scale: transform.matrix2.x_axis.x,
@@ -1909,10 +1910,7 @@ impl DocumentMessageHandler {
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartEvaluationQueue);
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
responses.add(NodeGraphMessage::SendGraph);
responses.add(Message::EndEvaluationQueue);
Some(previous_network)
}
@@ -1944,8 +1942,8 @@ impl DocumentMessageHandler {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendWirePaths);
responses.add(Message::EndEvaluationQueue);
Some(previous_network)
}
@@ -2576,7 +2574,7 @@ impl DocumentMessageHandler {
layout: Layout::WidgetLayout(document_bar_layout),
layout_target: LayoutTarget::DocumentBar,
});
responses.add(PortfolioMessage::EvaluateActiveDocument);
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
}
pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) {
@@ -138,87 +138,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("A default node network you can use to create your own custom nodes."),
properties: None,
},
DocumentNodeDefinition {
identifier: "Cache",
category: "General",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::None, true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
input_metadata: vec![("Data", "TODO").into()],
output_names: vec!["Data".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Merge",
category: "General",
@@ -529,21 +448,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
..Default::default()
},
]
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
skip_deduplication: true,
cache_output: true,
..Default::default()
}]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
@@ -556,24 +468,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
output_names: vec!["Image".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Canvas".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
node_metadata: [DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Canvas".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
]
..Default::default()
}]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
@@ -595,19 +497,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
manual_composition: Some(concrete!(Context)),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(concrete!(Context)),
cache_output: true,
..Default::default()
},
DocumentNode {
@@ -650,14 +547,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Rasterize".to_string(),
@@ -1408,33 +1297,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: vec![
DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
]
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: vec![DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
manual_composition: Some(generic!(T)),
cache_output: true,
..Default::default()
}]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
@@ -1450,40 +1320,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boolean Operation".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
node_metadata: [DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boolean Operation".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),
..Default::default()
},
..Default::default()
},
]
..Default::default()
}]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
@@ -1506,7 +1350,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(4), 0)],
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
@@ -1526,24 +1370,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::node(NodeId(0), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
cache_output: true,
manual_composition: Some(generic!(T)),
..Default::default()
},
@@ -1585,30 +1412,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -1671,96 +1474,17 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Vector: Modifier",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: [
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
NodeInput::network(concrete!(f64), 1),
NodeInput::network(concrete!(u32), 2),
],
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::U32(0), false),
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
NodeInput::network(concrete!(f64), 1),
NodeInput::network(concrete!(u32), 2),
],
cache_output: true,
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Poisson-Disk Points".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Freeze Real Time".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boundless Footprint".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
input_metadata: vec![
("Vector Data", "TODO").into(),
InputMetadata::with_name_description_override(
@@ -121,8 +121,8 @@ pub enum NodeGraphMessage {
},
SendClickTargets,
EndSendClickTargets,
UnloadWires,
SendWires,
UnloadWirePaths,
SendWirePaths,
UpdateVisibleNodes,
SendGraph,
SetGridAlignedEdges,
@@ -6,12 +6,12 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType};
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, FrontendNodeSNIUpdate};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
use crate::messages::portfolio::document::utility_types::network_interface::{self, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, Previewing, TypeSource};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, WireSNIUpdate, build_vector_wire};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
@@ -84,7 +84,7 @@ pub struct NodeGraphMessageHandler {
/// The end index of the moved port
end_index: Option<usize>,
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
frontend_nodes: Vec<NodeId>,
pub frontend_nodes: Vec<NodeId>,
/// Used to keep track of what wires are sent to the front end so the old ones can be removed
frontend_wires: HashSet<(NodeId, usize)>,
}
@@ -918,7 +918,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
wire_in_progress_to_connector,
from_connector_is_layer,
to_connector_is_layer,
GraphWireStyle::Direct,
&GraphWireStyle::Direct,
);
let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
@@ -928,7 +928,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
thick: false,
dashed: false,
center: None,
input_sni: None,
};
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) });
}
@@ -1179,7 +1178,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
{
return None;
}
let (wire, is_stack, _) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
let (wire, is_stack, _) = network_interface.vector_wire_from_input(&input, &preferences.graph_wire_style, selection_network_path)?;
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
})
.collect::<Vec<_>>();
@@ -1310,15 +1309,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
}),
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),
NodeGraphMessage::UnloadWires => {
NodeGraphMessage::UnloadWirePaths => {
for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) {
network_interface.unload_wire(&input, breadcrumb_network_path);
}
responses.add(FrontendMessage::ClearAllNodeGraphWires);
responses.add(FrontendMessage::ClearAllNodeGraphWirePaths);
}
NodeGraphMessage::SendWires => {
let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path);
NodeGraphMessage::SendWirePaths => {
let wires = self.collect_wires_paths(network_interface, &preferences.graph_wire_style, breadcrumb_network_path);
responses.add(FrontendMessage::UpdateNodeGraphWires { wires });
}
NodeGraphMessage::UpdateVisibleNodes => {
@@ -1347,8 +1346,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
self.frontend_nodes = nodes.iter().map(|node| node.id).collect();
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
responses.add(NodeGraphMessage::UpdateVisibleNodes);
let (layer_sni_updates, wire_sni_updates) = NodeGraphMessageHandler::graph_sni_updates(network_interface, breadcrumb_network_path);
responses.add(FrontendMessage::UpdateThumbnails {
add: Vec::new(),
clear: Vec::new(),
wire_sni_updates,
layer_sni_updates,
});
responses.add(NodeGraphMessage::UpdateVisibleNodes);
responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendWirePaths);
responses.add(FrontendMessage::UpdateGraphBreadcrumbPath {
breadcrumb_path: breadcrumb_network_path.to_vec(),
});
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
responses.add(NodeGraphMessage::UpdateImportsExports);
@@ -1366,6 +1377,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
network_interface.set_grid_aligned_edges(DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.), breadcrumb_network_path);
// Send the new edges to the frontend
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(NodeGraphMessage::SendWirePaths);
}
}
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
@@ -1378,7 +1390,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
if !(network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) {
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartEvaluationQueue);
responses.add(NodeGraphMessage::SendWires);
responses.add(NodeGraphMessage::SendWirePaths);
responses.add(Message::EndEvaluationQueue);
}
}
@@ -1437,7 +1449,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
}
}
responses.add(NodeGraphMessage::SendWires);
responses.add(NodeGraphMessage::SendWirePaths);
}
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
@@ -1458,7 +1470,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);
responses.add(NodeGraphMessage::SendWires);
responses.add(NodeGraphMessage::SendWirePaths);
}
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
@@ -1472,7 +1484,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
});
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::SendWires);
responses.add(NodeGraphMessage::SendWirePaths);
}
NodeGraphMessage::SetDisplayName {
node_id,
@@ -2130,7 +2142,30 @@ impl NodeGraphMessageHandler {
}
}
fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
pub fn graph_sni_updates(network_interface: &NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> (Vec<FrontendNodeSNIUpdate>, Vec<WireSNIUpdate>) {
let mut layer_updates = Vec::new();
let wires = network_interface
.node_graph_input_connectors(breadcrumb_network_path)
.iter()
.map(|input_connector| {
if let Some(node_id) = input_connector.node_id() {
if network_interface.is_layer(&node_id, breadcrumb_network_path) {
layer_updates.push(FrontendNodeSNIUpdate {
id: node_id,
sni: network_interface.protonode_from_output(&OutputConnector::node(node_id, 0), breadcrumb_network_path),
})
}
}
WireSNIUpdate {
id: input_connector.node_id().unwrap_or(NodeId(u64::MAX)),
input_index: input_connector.input_index(),
sni: network_interface.protonode_from_input(input_connector, breadcrumb_network_path),
}
})
.collect::<Vec<_>>();
(layer_updates, wires)
}
fn collect_wires_paths(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: &GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
let mut added_wires = network_interface
.node_graph_input_connectors(breadcrumb_network_path)
.iter()
@@ -2278,18 +2313,24 @@ impl NodeGraphMessageHandler {
let locked = network_interface.is_locked(&node_id, breadcrumb_network_path);
let errors = None; // TODO: Recursive traversal from export over all protonodes and match metadata with error
self.node_graph_errors
let errors: Option<String> = self
.node_graph_errors
.iter()
.find(|error| match &error.original_location {
graph_craft::proto::OriginalLocation::Value(_) => false,
graph_craft::proto::OriginalLocation::Node(node_ids) => node_ids == &node_id_path,
graph_craft::proto::OriginalLocation::Node(prefixed_node_path) => {
let (prefix, node_path) = prefixed_node_path.split_first().unwrap();
node_path == &node_id_path
}
})
.map(|error| format!("{:?}", error.error.clone()))
.or_else(|| {
if self.node_graph_errors.iter().any(|error| match &error.original_location {
graph_craft::proto::OriginalLocation::Value(_) => false,
graph_craft::proto::OriginalLocation::Node(node_ids) => node_ids.starts_with(&node_id_path),
graph_craft::proto::OriginalLocation::Node(prefixed_node_path) => {
let (prefix, node_path) = prefixed_node_path.split_first().unwrap();
node_path.starts_with(&node_id_path)
}
}) {
Some("Node graph type error within this node".to_string())
} else {
@@ -2297,11 +2338,16 @@ impl NodeGraphMessageHandler {
}
});
let is_layer = network_interface
.node_metadata(&node_id, breadcrumb_network_path)
.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer());
nodes.push(FrontendNode {
id: node_id,
is_layer: network_interface
.node_metadata(&node_id, breadcrumb_network_path)
.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()),
is_layer,
layer_thumbnail_sni: is_layer
.then(|| network_interface.protonode_from_input(&InputConnector::Node { node_id, input_index: 1 }, breadcrumb_network_path))
.flatten(),
can_be_layer: can_be_layer_lookup.contains(&node_id),
reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(),
display_name: network_interface.display_name(&node_id, breadcrumb_network_path),
@@ -1,8 +1,8 @@
use crate::messages::portfolio::document::utility_types::network_interface::{TypeSource};
use graph_craft::document::{InputConnector, OutputConnector};
use crate::messages::portfolio::document::utility_types::network_interface::TypeSource;
use graph_craft::document::value::TaggedValue;
use graphene_std::uuid::NodeId;
use graph_craft::document::{InputConnector, OutputConnector};
use graphene_std::Type;
use graphene_std::uuid::NodeId;
use std::borrow::Cow;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -76,6 +76,8 @@ pub struct FrontendNode {
pub id: NodeId,
#[serde(rename = "isLayer")]
pub is_layer: bool,
#[serde(rename = "layerThumbnailSNI")]
pub layer_thumbnail_sni: Option<NodeId>,
#[serde(rename = "canBeLayer")]
pub can_be_layer: bool,
pub reference: Option<String>,
@@ -98,6 +100,12 @@ pub struct FrontendNode {
pub ui_only: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeSNIUpdate {
pub id: NodeId,
pub sni: Option<NodeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType {
pub name: Cow<'static, str>,
@@ -2521,56 +2521,48 @@ impl NodeNetworkInterface {
.find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None })
}
pub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<WirePathUpdate> {
if !self.wire_is_loaded(input, network_path) {
self.load_wire(input, graph_wire_style, network_path);
} else {
pub fn viewport_loaded_thumbnail_position(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option<DVec2> {
let wire = self.wire_path_from_input(input, graph_wire_style, false, network_path)?;
let network_metadata = self.network_metadata(network_path)?;
if wire.thick {
return None;
}
};
wire.center.map(|center| {
let node_graph_position = DVec2::new(center.0 as f64, center.1 as f64);
network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(node_graph_position)
})
}
let wire = match input {
pub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option<WirePathUpdate> {
match self.cached_wire(input, network_path) {
Some(loaded) => None,
None => {
self.load_wire(input, graph_wire_style, network_path);
self.cached_wire(input, network_path).cloned()
}
}
}
pub fn cached_wire(&self, input: &InputConnector, network_path: &[NodeId]) -> Option<&WirePathUpdate> {
match input {
InputConnector::Node { node_id, input_index } => {
let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?;
let TransientMetadata::Loaded(wire) = &input_metadata.wire else {
log::error!("Could not load wire for input: {:?}", input);
return None;
};
wire.clone()
Some(wire)
}
InputConnector::Export(export_index) => {
let network_metadata = self.network_metadata(network_path)?;
let Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else {
log::error!("Could not load wire for input: {:?}", input);
return None;
};
wire.clone()
}
};
Some(wire)
}
pub fn wire_is_loaded(&mut self, input: &InputConnector, network_path: &[NodeId]) -> bool {
match input {
InputConnector::Node { node_id, input_index } => {
let Some(input_metadata) = self.transient_input_metadata(node_id, *input_index, network_path) else {
log::error!("Input metadata should always exist for input");
return false;
};
input_metadata.wire.is_loaded()
}
InputConnector::Export(export_index) => {
let Some(network_metadata) = self.network_metadata(network_path) else {
return false;
};
match network_metadata.transient_metadata.wires.get(*export_index) {
Some(wire) => wire.is_loaded(),
None => false,
}
Some(wire)
}
}
}
fn load_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) {
fn load_wire(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) {
let dashed = match self.previewing(network_path) {
Previewing::Yes { .. } => match input {
InputConnector::Node { .. } => false,
@@ -2698,7 +2690,7 @@ impl NodeNetworkInterface {
}
/// When previewing, there may be a second path to the root node.
pub fn wire_to_root(&mut self, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<WirePathUpdate> {
pub fn wire_to_root(&mut self, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option<WirePathUpdate> {
let input = InputConnector::Export(0);
let current_export = self.upstream_output_connector(&input, network_path)?;
@@ -2733,7 +2725,6 @@ impl NodeNetworkInterface {
thick,
dashed: false,
center: None,
input_sni: None,
});
Some(WirePathUpdate {
@@ -2744,7 +2735,7 @@ impl NodeNetworkInterface {
}
/// Returns the vector subpath and a boolean of whether the wire should be thick.
pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath<PointId>, bool, DVec2)> {
pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath<PointId>, bool, DVec2)> {
let Some(input_position) = self.get_input_center(input, network_path) else {
log::error!("Could not get dom rect for wire end: {:?}", input);
return None;
@@ -2760,23 +2751,21 @@ impl NodeNetworkInterface {
let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0);
let vertical_start = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path));
let thick = vertical_end && vertical_start;
let (wire, center) = build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style);
let (wire, center) = build_vector_wire(output_position, input_position, vertical_start, vertical_end, &wire_style);
Some((wire, thick, center))
}
pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option<WirePath> {
pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option<WirePath> {
let (vector_wire, thick, center) = self.vector_wire_from_input(input, graph_wire_style, network_path)?;
let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0);
let input_sni = self.protonode_from_input(input, network_path);
Some(WirePath {
path_string,
data_type,
thick,
dashed,
center: Some((center.x, center.y)),
input_sni,
center: Some((center.x as i32, center.y as i32)),
})
}
@@ -12,9 +12,7 @@ pub struct WirePath {
pub data_type: FrontendGraphDataType,
pub thick: bool,
pub dashed: bool,
pub center: Option<(f64, f64)>,
#[serde(rename = "inputSni")]
pub input_sni: Option<NodeId>,
pub center: Option<(i32, i32)>,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -27,6 +25,14 @@ pub struct WirePathUpdate {
pub wire_path_update: Option<WirePath>,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct WireSNIUpdate {
pub id: NodeId,
#[serde(rename = "inputIndex")]
pub input_index: usize,
pub sni: Option<NodeId>,
}
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum GraphWireStyle {
#[default]
@@ -56,7 +62,7 @@ impl GraphWireStyle {
}
}
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> (Subpath<PointId>, DVec2) {
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: &GraphWireStyle) -> (Subpath<PointId>, DVec2) {
let grid_spacing = 24.;
match graph_wire_style {
GraphWireStyle::Direct => {
@@ -394,14 +394,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::transform_nodes::transform::IDENTIFIER,
aliases: &["graphene_core::transform::TransformNode"],
},
NodeReplacement {
node: graphene_std::transform_nodes::boundless_footprint::IDENTIFIER,
aliases: &["graphene_core::transform::BoundlessFootprintNode"],
},
NodeReplacement {
node: graphene_std::transform_nodes::freeze_real_time::IDENTIFIER,
aliases: &["graphene_core::transform::FreezeRealTimeNode"],
},
// ???
NodeReplacement {
node: graphene_std::vector::spline::IDENTIFIER,
@@ -9,7 +9,7 @@ use graphene_std::Color;
use graphene_std::raster::Image;
use graphene_std::renderer::RenderMetadata;
use graphene_std::text::Font;
use graphene_std::uuid::{SNI};
use graphene_std::uuid::SNI;
#[impl_message(Message, Portfolio)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -22,17 +22,18 @@ pub enum PortfolioMessage {
#[child]
Spreadsheet(SpreadsheetMessage),
// Introspected data is cleared after all queued messages which relied on the introspection are complete
ClearIntrospectedData,
// Sends a request to compile the network. Should occur when any value, preference, or font changes
CompileActiveDocument,
// Sends a request to evaluate the network. Should occur when any context value changes.
EvaluateActiveDocument,
// Sends a request to introspect data in the network, and return it to the editor
IntrospectActiveDocument {
// Nodes to introspect is in addition to all nodes to render thumbnails for
EvaluateActiveDocumentWithThumbnails,
EvaluateActiveDocument {
nodes_to_introspect: HashSet<SNI>,
},
// Sends a request to introspect data in the network, and return it to the editor
// IntrospectActiveDocument {
// nodes_to_introspect: HashSet<SNI>,
// },
ExportActiveDocument {
file_name: String,
file_type: FileType,
@@ -47,12 +48,11 @@ pub enum PortfolioMessage {
},
ProcessEvaluationResponse {
evaluation_metadata: RenderMetadata,
},
ProcessIntrospectionResponse {
#[serde(skip)]
introspection_response: IntrospectionResponse,
introspected_nodes: IntrospectionResponse,
},
RenderThumbnails,
// Introspected data is cleared after queued messages are complete
ClearIntrospectedData,
ProcessThumbnails,
DocumentPassMessage {
document_id: DocumentId,
@@ -12,6 +12,7 @@ use crate::messages::portfolio::document::DocumentMessageContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WireSNIUpdate};
use crate::messages::portfolio::document_migration::*;
use crate::messages::portfolio::spreadsheet::SpreadsheetMessageHandlerData;
use crate::messages::preferences::SelectionMode;
@@ -21,8 +22,9 @@ use crate::node_graph_executor::{CompilationRequest, ExportConfig, NodeGraphExec
use glam::{DAffine2, DVec2};
use graph_craft::document::value::EditorMetadata;
use graph_craft::document::{InputConnector, NodeInput, OutputConnector};
use graphene_std::any::EditorContext;
use graphene_std::EditorContext;
use graphene_std::application_io::TimingInformation;
use graphene_std::memo::MonitorIntrospectResult;
use graphene_std::renderer::{Quad, RenderMetadata};
use graphene_std::text::Font;
use graphene_std::transform::{Footprint, RenderQuality};
@@ -57,10 +59,10 @@ pub struct PortfolioMessageHandler {
device_pixel_ratio: Option<f64>,
pub reset_node_definitions_on_open: bool,
// Data from the node graph, which is populated after an introspection request.
// To access the data, schedule messages with StartIntrospectionQueue [messages] EndIntrospectionQueue
// The data is no longer accessible after EndIntrospectionQueue
pub introspected_data: HashMap<SNI, Option<Arc<dyn std::any::Any + Send + Sync>>>,
pub previous_thumbnail_data: HashMap<SNI, Arc<dyn std::any::Any + Send + Sync>>,
// To access the data, schedule messages with StartEvaluationQueue [messages] EndEvaluationQueue
// The data is no longer accessible after EndEvaluationQueue
pub introspected_data: HashMap<SNI, MonitorIntrospectResult>,
thumbnails_to_clear: Vec<SNI>,
}
#[message_handler_data]
@@ -791,8 +793,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
transform_to_viewport: true,
},
});
// Also evaluate the document after compilation
responses.add_front(PortfolioMessage::EvaluateActiveDocument);
}
}
PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => {
@@ -814,67 +814,23 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// Remove all thumbnails
cleared_thumbnails.push(sni);
}
responses.add(FrontendMessage::UpdateThumbnails {
add: Vec::new(),
clear: cleared_thumbnails,
});
self.thumbnails_to_clear.extend(cleared_thumbnails);
}
PortfolioMessage::EvaluateActiveDocument => {
PortfolioMessage::EvaluateActiveDocumentWithThumbnails => {
responses.add(PortfolioMessage::EvaluateActiveDocument {
nodes_to_introspect: self.nodes_to_try_render(ipp.viewport_bounds()[1], &preferences.graph_wire_style),
});
responses.add(Message::StartEvaluationQueue);
responses.add(PortfolioMessage::ProcessThumbnails);
responses.add(Message::EndEvaluationQueue);
}
PortfolioMessage::EvaluateActiveDocument { nodes_to_introspect } => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
// Get all the inputs to save data for. This includes vector modify, thumbnails, and spreadsheet data
// Save vector data for all path/transform nodes in the document network
// match document.network_interface.input_from_connector(&InputConnector::Export(0), &[]) {
// Some(NodeInput::Node { node_id, .. }) => {
// for upstream_node in document.network_interface.upstream_flow_back_from_nodes(vec![*node_id], &[], network_interface::FlowType::UpstreamFlow) {
// let reference = document.network_interface.reference(&upstream_node, &[]).and_then(|reference| reference.as_deref());
// if reference == Some("Path") || reference == Some("Transform") {
// let input_connector = InputConnector::Node { node_id: *node_id, input_index: 0 };
// let Some(downstream_caller) = document.network_interface.downstream_caller_from_input(&input_connector, &[]) else {
// log::error!("could not get downstream caller for node : {:?}", node_id);
// continue;
// };
// inputs_to_monitor.insert((*downstream_caller, IntrospectMode::Data));
// }
// }
// }
// _ => {}
// }
// Introspect data for the currently selected node (eventually thumbnail) if the spreadsheet view is open
// if self.spreadsheet.spreadsheet_view_open {
// let selected_network_path = &document.selection_network_path;
// // TODO: Replace with selected thumbnail
// if let Some(selected_node) = document
// .network_interface
// .selected_nodes_in_nested_network(selected_network_path)
// .and_then(|selected_nodes| if selected_nodes.0.len() == 1 { selected_nodes.0.first().copied() } else { None })
// {
// // TODO: Introspect any input rather than just the first input of the selected node
// let selected_connector = InputConnector::Node {
// node_id: selected_node,
// input_index: 0,
// };
// match document.network_interface.downstream_caller_from_input(&selected_connector, selected_network_path) {
// Some(caller) => {
// inputs_to_monitor.insert((*caller, IntrospectMode::Data));
// inspect_input = Some(InspectInputConnector {
// input_connector: AbsoluteInputConnector {
// network_path: selected_network_path.clone(),
// connector: selected_connector,
// },
// protonode_input: *caller,
// });
// }
// None => log::error!("Could not get downstream caller for {:?}", selected_connector),
// }
// };
// }
// let animation_time = match animation.timing_information().animation_time {
// AnimationState::Stopped => 0.,
// AnimationState::Playing { start } => ipp.time - start,
@@ -891,9 +847,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
context.real_time = Some(ipp.time);
context.downstream_transform = Some(DAffine2::IDENTITY);
self.executor.submit_node_graph_evaluation(context, None, None);
let nodes_to_try_render = self.nodes_to_try_render(ipp.viewport_bounds()[1], &preferences.graph_wire_style);
self.executor.submit_node_graph_evaluation(context, None, None, nodes_to_try_render);
}
PortfolioMessage::ProcessEvaluationResponse { evaluation_metadata } => {
PortfolioMessage::ProcessEvaluationResponse {
evaluation_metadata,
introspected_nodes,
} => {
for (protonode, data) in introspected_nodes.0.into_iter() {
self.introspected_data.insert(protonode, data);
}
let RenderMetadata {
upstream_footprints: footprints,
local_transforms,
@@ -915,104 +880,71 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// AnimationState::Playing { .. } => responses.add(PortfolioMessage::EvaluateActiveDocument),
// _ => {}
// };
// After an evaluation, always render all thumbnails
responses.add(PortfolioMessage::RenderThumbnails);
}
PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => {
self.executor.submit_node_graph_introspection(nodes_to_introspect);
}
PortfolioMessage::ProcessIntrospectionResponse { introspection_response } => {
for (protonode, data) in introspection_response.0.into_iter() {
self.introspected_data.insert(protonode, data);
}
}
// PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => {
// self.executor.submit_node_graph_introspection(nodes_to_introspect);
// }
// PortfolioMessage::RenderThumbnails => {
// let nodes_to_render = self.nodes_to_render();
// responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect: nodes_to_render });
// responses.add(Message::StartIntrospectionQueue);
// responses.add(PortfolioMessage::ProcessThumbnails);
// responses.add(Message::EndIntrospectionQueue);
// }
PortfolioMessage::ClearIntrospectedData => self.introspected_data.clear(),
PortfolioMessage::RenderThumbnails => {
PortfolioMessage::ProcessThumbnails => {
let mut thumbnail_response = ThumbnailRenderResponse::default();
for (thumbnail_node, monitor_result) in self.introspected_data.drain() {
let evaluated_data = match monitor_result {
MonitorIntrospectResult::Error => continue,
MonitorIntrospectResult::Disabled => continue,
MonitorIntrospectResult::NotEvaluated => continue,
MonitorIntrospectResult::Evaluated((data, changed)) => {
// If the evaluated value is the same as the previous, then just remap the ID
if !changed {
continue;
}
data
}
};
match graph_craft::document::value::render_thumbnail(&evaluated_data) {
Some(thumbnail) => thumbnail_response.add.push((thumbnail_node, thumbnail)),
None => thumbnail_response.clear.push(thumbnail_node),
}
}
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
// All possible inputs, later check if they are connected to any nodes
let mut nodes_to_render = HashSet::new();
// Get all inputs to render thumbnails for
// Get all protonodes for all connected side layer inputs connected to the export in the document network
for layer in document.network_interface.document_metadata().all_layers() {
let connector = InputConnector::Node {
node_id: layer.to_node(),
let mut wire_sni_updates = document
.network_interface
.document_metadata()
.all_layers()
.map(|layer| WireSNIUpdate {
id: layer.to_node(),
input_index: 1,
};
if document.network_interface.input_from_connector(&connector, &[]).is_some_and(|input| input.is_wire()) {
if let Some(compiled_input) = document.network_interface.protonode_from_input(&connector, &[]) {
nodes_to_render.insert(compiled_input);
}
}
}
sni: document.network_interface.protonode_from_input(&InputConnector::node(layer.to_node(), 1), &[]),
})
.collect::<Vec<_>>();
let mut layer_sni_updates = Vec::new();
// Save data for all inputs in the viewed node graph
// Only update wires/nodes in the node graph if they are open, but this will require syncing when the node graph resent.
if document.graph_view_overlay_open {
let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else {
return;
};
let mut wire_stack = viewed_network
.exports
.iter()
.enumerate()
.filter_map(|(export_index, export)| export.is_wire().then_some(InputConnector::Export(export_index)))
.collect::<Vec<_>>();
while let Some(input_connector) = wire_stack.pop() {
let Some(input) = document.network_interface.input_from_connector(&input_connector, &document.breadcrumb_network_path) else {
log::error!("Could not get input from connector: {:?}", input_connector);
continue;
};
if let NodeInput::Node { node_id, .. } = input {
let Some(node) = document.network_interface.document_node(node_id, &document.breadcrumb_network_path) else {
log::error!("Could not get node");
continue;
};
for (wire_input_index, _) in node.inputs.iter().enumerate().filter(|(_, input)| input.is_wire()) {
wire_stack.push(InputConnector::Node {
node_id: *node_id,
input_index: wire_input_index,
})
}
};
let Some(protonode) = document.network_interface.protonode_from_input(&input_connector, &document.breadcrumb_network_path) else {
// The protonode has not been compiled, so it is not connected to the export
wire_stack = Vec::new();
continue;
};
nodes_to_render.insert(protonode);
}
};
responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect: nodes_to_render });
responses.add(Message::StartIntrospectionQueue);
responses.add(PortfolioMessage::ProcessThumbnails);
responses.add(Message::EndIntrospectionQueue);
}
PortfolioMessage::ProcessThumbnails => {
let mut thumbnail_response = ThumbnailRenderResponse::default();
for (thumbnail_node, introspected_data) in self.introspected_data.drain() {
let Some(evaluated_data) = introspected_data else {
// Input was not evaluated, do not change its thumbnail
continue;
};
let previous_thumbnail_data = self.previous_thumbnail_data.get(&thumbnail_node);
match graph_craft::document::value::render_thumbnail_if_change(&evaluated_data, previous_thumbnail_data) {
graph_craft::document::value::ThumbnailRenderResult::NoChange => return,
graph_craft::document::value::ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(thumbnail_node),
graph_craft::document::value::ThumbnailRenderResult::UpdateThumbnail(thumbnail) => thumbnail_response.add.push((thumbnail_node, thumbnail)),
}
self.previous_thumbnail_data.insert(thumbnail_node, evaluated_data);
let (layer_updates, wire_updates) = NodeGraphMessageHandler::graph_sni_updates(&document.network_interface, &document.breadcrumb_network_path);
layer_sni_updates.extend(layer_updates);
wire_sni_updates.extend(wire_updates);
}
let mut clear = std::mem::take(&mut self.thumbnails_to_clear);
clear.extend(thumbnail_response.clear);
responses.add(FrontendMessage::UpdateThumbnails {
add: thumbnail_response.add,
clear: thumbnail_response.clear,
})
clear,
wire_sni_updates,
layer_sni_updates,
});
}
PortfolioMessage::ExportActiveDocument {
file_name,
@@ -1081,6 +1013,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
transparent_background,
size: scaled_size,
}),
HashSet::new(),
);
// if let Some((start, end, fps)) = animation_export_data {
@@ -1300,11 +1233,100 @@ impl PortfolioMessageHandler {
/text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string();
responses.add(Message::ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata::default()));
responses.add(Message::ProcessEvaluationQueue(
graphene_std::renderer::RenderMetadata::default(),
crate::node_graph_executor::IntrospectionResponse(Vec::new()),
));
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
}
result
}
pub fn nodes_to_try_render(&mut self, viewport_size: DVec2, graph_wire_style: &GraphWireStyle) -> HashSet<SNI> {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get_mut(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return HashSet::new();
};
// All possible inputs, later check if they are connected to any nodes
let mut nodes_to_render = HashSet::new();
if document.graph_view_overlay_open {
let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else {
return HashSet::new();
};
let mut wire_stack = viewed_network
.exports
.iter()
.enumerate()
.filter_map(|(export_index, export)| export.is_wire().then_some(InputConnector::Export(export_index)))
.collect::<Vec<_>>();
while let Some(input_connector) = wire_stack.pop() {
let Some(input) = document.network_interface.input_from_connector(&input_connector, &document.breadcrumb_network_path) else {
log::error!("Could not get input from connector: {:?}", input_connector);
continue;
};
if let NodeInput::Node { node_id, .. } = input {
let Some(node) = document.network_interface.document_node(node_id, &document.breadcrumb_network_path) else {
log::error!("Could not get node");
continue;
};
for (wire_input_index, _) in node.inputs.iter().enumerate().filter(|(_, input)| input.is_wire()) {
wire_stack.push(InputConnector::Node {
node_id: *node_id,
input_index: wire_input_index,
})
}
};
// Save data for all thin wires with visible thumbnails in the viewed node graph
if let Some(viewport_position) = document
.network_interface
.viewport_loaded_thumbnail_position(&input_connector, graph_wire_style, &document.breadcrumb_network_path)
{
log::debug!("viewport position: {:?}, input: {:?}", viewport_position, input_connector);
let in_view = viewport_position.x < 0.0 || viewport_position.y < 0.0 || viewport_position.x > viewport_size.x || viewport_position.y > viewport_size.y;
if in_view {
let Some(protonode) = document.network_interface.protonode_from_input(&input_connector, &document.breadcrumb_network_path) else {
// The input is not connected to the export, which occurs if inside a disconnected node
wire_stack = Vec::new();
nodes_to_render.clear();
continue;
};
nodes_to_render.insert(protonode);
}
}
}
};
// Get thumbnails for all visible layer
for visible_node in &document.node_graph_handler.frontend_nodes {
if document.network_interface.is_layer(&visible_node, &document.breadcrumb_network_path) {
log::debug!("visible_node: {:?}", visible_node);
let Some(protonode) = document
.network_interface
.protonode_from_output(&OutputConnector::node(*visible_node, 1), &document.breadcrumb_network_path)
else {
continue;
};
nodes_to_render.insert(protonode);
}
}
// Get all protonodes for all connected side layer inputs connected to the export in the document network
for layer in document.network_interface.document_metadata().all_layers() {
let connector = InputConnector::Node {
node_id: layer.to_node(),
input_index: 1,
};
if document.network_interface.input_from_connector(&connector, &[]).is_some_and(|input| input.is_wire()) {
if let Some(protonode) = document.network_interface.protonode_from_input(&connector, &[]) {
nodes_to_render.insert(protonode);
}
}
}
nodes_to_render
}
}
#[derive(Clone, Debug, Default)]
@@ -7,6 +7,7 @@ use graph_craft::document::OutputConnector;
use graphene_std::Color;
use graphene_std::GraphicGroupTable;
use graphene_std::instances::Instances;
use graphene_std::memo::MonitorIntrospectResult;
use graphene_std::raster::Image;
use graphene_std::uuid::{NodeId, SNI};
use graphene_std::vector::{VectorData, VectorDataTable};
@@ -15,7 +16,7 @@ use std::sync::Arc;
#[derive(ExtractField)]
pub struct SpreadsheetMessageHandlerData<'a> {
pub introspected_data: &'a HashMap<SNI, Option<Arc<dyn std::any::Any + Send + Sync>>>,
pub introspected_data: &'a HashMap<SNI, MonitorIntrospectResult>,
// Network interface of the selected document
pub network_interface: &'a NodeNetworkInterface,
}
@@ -26,7 +27,7 @@ pub struct SpreadsheetMessageHandler {
/// Sets whether or not the spreadsheet is drawn.
pub spreadsheet_view_open: bool,
// Path to the document node that is introspected. The protonode is found by traversing from the primary output
inspection_data: Option<Option<Arc<dyn std::any::Any + Send + Sync>>>,
inspection_data: Option<MonitorIntrospectResult>,
node_to_inspect: Option<NodeId>,
instances_path: Vec<usize>,
@@ -73,10 +74,10 @@ impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData<'_>> for S
let mut nodes_to_introspect = HashSet::new();
nodes_to_introspect.insert(protonode_id);
responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect });
responses.add(Message::StartIntrospectionQueue);
responses.add(PortfolioMessage::EvaluateActiveDocument { nodes_to_introspect });
responses.add(Message::StartEvaluationQueue);
responses.add(SpreadsheetMessage::ProcessUpdateLayout { node_to_inspect, protonode_id });
responses.add(Message::EndIntrospectionQueue);
responses.add(Message::EndEvaluationQueue);
self.update_layout(responses);
}
@@ -93,7 +94,6 @@ impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData<'_>> for S
self.instances_path.truncate(len);
self.update_layout(responses);
}
SpreadsheetMessage::ViewVectorDataDomain { domain } => {
self.viewing_vector_data_domain = domain;
self.update_layout(responses);
@@ -126,11 +126,13 @@ impl SpreadsheetMessageHandler {
Some(_) => {
match &self.inspection_data {
Some(data) => match data {
Some(inspected_data) => match generate_layout(&inspected_data, &mut layout_data) {
MonitorIntrospectResult::Error => label("The introspected node is a type that cannot be cloned"),
MonitorIntrospectResult::Disabled => label("Error: The introspected node must be set to StoreFirstEvaluation before introspection"),
MonitorIntrospectResult::NotEvaluated => label("Introspected data is not available for this input. This input may be cached."),
MonitorIntrospectResult::Evaluated((data, _)) => match generate_layout(data, &mut layout_data) {
Some(layout) => layout,
None => label("The introspected data is not a supported type to be displayed."),
},
None => label("Introspected data is not available for this input. This input may be cached."),
},
// There should always be an entry for each protonode input. If its empty then it was not requested or an error occured
None => label("The output of this node could not be determined"),
@@ -82,8 +82,8 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
}
PreferencesMessage::GraphWireStyle { style } => {
self.graph_wire_style = style;
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendWirePaths);
}
PreferencesMessage::ViewportZoomWheelRate { rate } => {
self.viewport_zoom_wheel_rate = rate;
+81 -50
View File
@@ -6,7 +6,8 @@ use dyn_any::DynAny;
use graph_craft::document::value::{EditorMetadata, RenderOutput, TaggedValue};
use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid};
use graph_craft::proto::GraphErrors;
use graphene_std::any::EditorContext;
use graphene_std::EditorContext;
use graphene_std::memo::MonitorIntrospectResult;
use graphene_std::renderer::format_transform_matrix;
use graphene_std::text::FontCache;
use graphene_std::uuid::SNI;
@@ -30,6 +31,11 @@ pub struct CompilationResponse {
node_graph_errors: GraphErrors,
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct CacheEnableRequest {
nodes_to_enable: HashSet<SNI>,
}
// Metadata the editor sends when evaluating the network
#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
pub struct EvaluationRequest {
@@ -37,16 +43,18 @@ pub struct EvaluationRequest {
#[serde(skip)]
pub context: EditorContext,
pub node_to_evaluate: Option<SNI>,
pub nodes_to_introspect: HashSet<SNI>,
}
// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub struct EvaluationResponse {
evaluation_id: u64,
result: Result<TaggedValue, String>,
introspected_nodes: IntrospectionResponse,
}
#[derive(Debug, Clone, Default)]
pub struct IntrospectionResponse(pub Vec<(SNI, Option<Arc<dyn std::any::Any + Send + Sync>>)>);
pub struct IntrospectionResponse(pub Vec<(SNI, MonitorIntrospectResult)>);
impl PartialEq for IntrospectionResponse {
fn eq(&self, _other: &Self) -> bool {
@@ -58,7 +66,6 @@ impl PartialEq for IntrospectionResponse {
pub enum NodeGraphUpdate {
CompilationResponse(CompilationResponse),
EvaluationResponse(EvaluationResponse),
IntrospectionResponse(IntrospectionResponse),
}
#[derive(Debug, Default)]
@@ -83,18 +90,18 @@ impl Default for NodeGraphExecutor {
impl NodeGraphExecutor {
/// A local runtime is useful on threads since having global state causes flakes
#[cfg(test)]
pub(crate) fn new_with_local_runtime() -> (NodeRuntime, Self) {
let (request_sender, request_receiver) = std::sync::mpsc::channel();
let (response_sender, response_receiver) = std::sync::mpsc::channel();
let node_runtime = NodeRuntime::new(request_receiver, response_sender);
// #[cfg(test)]
// pub(crate) fn new_with_local_runtime() -> (NodeRuntime, Self) {
// let (request_sender, request_receiver) = std::sync::mpsc::channel();
// let (response_sender, response_receiver) = std::sync::mpsc::channel();
// let node_runtime = NodeRuntime::new(request_receiver, response_sender);
let node_executor = Self {
futures: HashMap::new(),
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
};
(node_runtime, node_executor)
}
// let node_executor = Self {
// futures: HashMap::new(),
// runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
// };
// (node_runtime, node_executor)
// }
/// Updates the network to monitor all inputs. Useful for the testing.
// #[cfg(test)]
@@ -110,19 +117,28 @@ impl NodeGraphExecutor {
/// Compile the network
pub fn submit_node_graph_compilation(&mut self, compilation_request: CompilationRequest) {
if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::CompilationRequest(compilation_request)) {
if let Err(error) = self.runtime_io.try_send(GraphRuntimeRequest::CompilationRequest(compilation_request)) {
log::error!("Could not send evaluation request. {:?}", error);
return;
}
}
// // Adds a request to set disabled cache nodes (automatically placed on the output of each node) to save the value of their first exeuction
// pub fn submit_node_graph_cache_enable(&mut self, nodes_to_enable: HashSet<SNI>) {
// if let Err(error) = self.runtime_io.try_send(GraphRuntimeRequest::CacheEnableRequest(CacheEnableRequest { nodes_to_enable })) {
// log::error!("Could not send evaluation request. {:?}", error);
// return;
// }
// }
/// Adds an evaluation request for whatever current network is cached.
pub fn submit_node_graph_evaluation(&mut self, context: EditorContext, node_to_evaluate: Option<SNI>, export_config: Option<ExportConfig>) {
pub fn submit_node_graph_evaluation(&mut self, context: EditorContext, node_to_evaluate: Option<SNI>, export_config: Option<ExportConfig>, nodes_to_introspect: HashSet<SNI>) {
let evaluation_id = generate_uuid();
if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::EvaluationRequest(EvaluationRequest {
if let Err(error) = self.runtime_io.try_send(GraphRuntimeRequest::EvaluationRequest(EvaluationRequest {
evaluation_id,
context,
node_to_evaluate,
nodes_to_introspect,
})) {
log::error!("Could not send evaluation request. {:?}", error);
return;
@@ -131,18 +147,43 @@ impl NodeGraphExecutor {
self.futures.insert(evaluation_id, evaluation_context);
}
pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet<SNI>) {
if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::IntrospectionRequest(nodes_to_introspect)) {
log::error!("Could not send evaluation request. {:?}", error);
return;
}
}
// pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet<SNI>) {
// if let Err(error) = self.runtime_io.try_send(GraphRuntimeRequest::IntrospectionRequest(nodes_to_introspect)) {
// log::error!("Could not send evaluation request. {:?}", error);
// return;
// }
// }
// Continuously poll the executor (called by request animation frame)
pub fn poll_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Result<(), String> {
for response in self.runtime_io.receive() {
if let Ok(response) = self.runtime_io.receive() {
self.runtime_io.busy = false;
match response {
NodeGraphUpdate::EvaluationResponse(EvaluationResponse { evaluation_id, result }) => {
NodeGraphUpdate::CompilationResponse(compilation_response) => {
let CompilationResponse { node_graph_errors, result } = compilation_response;
let compilation_metadata = match result {
Err(e) => {
// Clear the click targets while the graph is in an un-renderable state
document.network_interface.update_click_targets(HashMap::new());
document.network_interface.update_vector_modify(HashMap::new());
document.node_graph_handler.node_graph_errors = node_graph_errors;
responses.add(NodeGraphMessage::SendGraph);
log::trace!("{e}");
return Err(format!("Node graph evaluation failed:\n{e}"));
}
Ok(result) => result,
};
// Always evaluate after a compilation
responses.add_front(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
responses.add_front(PortfolioMessage::ProcessCompilationResponse { compilation_metadata });
}
NodeGraphUpdate::EvaluationResponse(EvaluationResponse {
evaluation_id,
result,
introspected_nodes,
}) => {
responses.add(OverlaysMessage::Draw);
let node_graph_output = match result {
@@ -186,36 +227,17 @@ impl NodeGraphExecutor {
}
} else {
// Update artwork
self.process_node_graph_output(render_output, responses)?
self.process_node_graph_output(render_output, introspected_nodes, responses)?
}
}
NodeGraphUpdate::CompilationResponse(compilation_response) => {
let CompilationResponse { node_graph_errors, result } = compilation_response;
let compilation_metadata = match result {
Err(e) => {
// Clear the click targets while the graph is in an un-renderable state
document.network_interface.update_click_targets(HashMap::new());
document.network_interface.update_vector_modify(HashMap::new());
document.node_graph_handler.node_graph_errors = node_graph_errors;
responses.add(NodeGraphMessage::SendGraph);
log::trace!("{e}");
return Err(format!("Node graph evaluation failed:\n{e}"));
}
Ok(result) => result,
};
responses.add(PortfolioMessage::ProcessCompilationResponse { compilation_metadata });
}
NodeGraphUpdate::IntrospectionResponse(introspection_response) => {
responses.add(Message::ProcessIntrospectionQueue(introspection_response));
}
} // NodeGraphUpdate::IntrospectionResponse(introspection_response) => {
// responses.add_front(Message::ProcessIntrospectionQueue(introspection_response));
// }
}
}
Ok(())
}
fn process_node_graph_output(&self, render_output: RenderOutput, responses: &mut VecDeque<Message>) -> Result<(), String> {
fn process_node_graph_output(&self, render_output: RenderOutput, introspected_nodes: IntrospectionResponse, responses: &mut VecDeque<Message>) -> Result<(), String> {
match render_output.data {
graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => {
// Send to frontend
@@ -234,7 +256,16 @@ impl NodeGraphExecutor {
return Err(format!("Invalid node graph output type: {:#?}", render_output.data));
}
}
responses.add(Message::ProcessEvaluationQueue(render_output.metadata));
let context_during_evaluation = self
.runtime_io
.context_receiver
.try_iter()
.map(|(ids, index, context)| (ids, index, format!("{:?}", context)))
.collect::<Vec<_>>();
if context_during_evaluation.len() != 0 {
responses.add_front(FrontendMessage::UpdateContextDuringEvaluation { context_during_evaluation });
}
responses.add_front(Message::ProcessEvaluationQueue(render_output.metadata, introspected_nodes));
Ok(())
}
}
+34 -33
View File
@@ -24,6 +24,7 @@ pub struct NodeRuntime {
executor: DynamicExecutor,
receiver: Receiver<GraphRuntimeRequest>,
sender: NodeGraphRuntimeSender,
context_sender: Sender<(SNI, usize, EditorContext)>,
application_io: Option<Arc<WasmApplicationIo>>,
@@ -46,7 +47,6 @@ pub enum GraphRuntimeRequest {
// ThumbnailRenderRequest(HashSet<CompiledProtonodeInput>),
// Request the data from a list of node inputs. For example, used by vector modify to get the data at the input of every Path node.
// Can also be used by the spreadsheet/introspection system
IntrospectionRequest(HashSet<SNI>),
}
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -69,19 +69,20 @@ impl NodeGraphRuntimeSender {
fn send_evaluation_response(&self, response: EvaluationResponse) {
self.0.send(NodeGraphUpdate::EvaluationResponse(response)).expect("Failed to send evaluation response")
}
fn send_introspection_response(&self, response: IntrospectionResponse) {
self.0.send(NodeGraphUpdate::IntrospectionResponse(response)).expect("Failed to send introspection response")
}
// fn send_introspection_response(&self, response: IntrospectionResponse) {
// self.0.send(NodeGraphUpdate::IntrospectionResponse(response)).expect("Failed to send introspection response")
// }
}
pub static NODE_RUNTIME: Lazy<Mutex<Option<NodeRuntime>>> = Lazy::new(|| Mutex::new(None));
impl NodeRuntime {
pub fn new(receiver: Receiver<GraphRuntimeRequest>, sender: Sender<NodeGraphUpdate>) -> Self {
pub fn new(receiver: Receiver<GraphRuntimeRequest>, sender: Sender<NodeGraphUpdate>, context_sender: Sender<(SNI, usize, EditorContext)>) -> Self {
Self {
executor: DynamicExecutor::default(),
receiver,
sender: NodeGraphRuntimeSender(sender.clone()),
context_sender,
application_io: None,
@@ -100,24 +101,26 @@ impl NodeRuntime {
}
// TODO: This deduplication of messages may cause issues
let mut compilation = None;
let mut evaluation = None;
let mut introspection = None;
for request in self.receiver.try_iter() {
match request {
GraphRuntimeRequest::CompilationRequest(_) => compilation = Some(request),
GraphRuntimeRequest::EvaluationRequest(_) => evaluation = Some(request),
GraphRuntimeRequest::IntrospectionRequest(_) => introspection = Some(request),
}
}
let requests = [compilation, evaluation, introspection].into_iter().flatten();
// let mut compilation = None;
// let mut cache_enable = None;
// let mut evaluation = None;
// let mut introspection = None;
// for request in self.receiver.try_iter() {
// match request {
// GraphRuntimeRequest::CompilationRequest(_) => compilation = Some(request),
// GraphRuntimeRequest::CacheEnableRequest(_) => cache_enable = Some(request),
// GraphRuntimeRequest::EvaluationRequest(_) => evaluation = Some(request),
// GraphRuntimeRequest::IntrospectionRequest(_) => introspection = Some(request),
// }
// }
for request in requests {
// let requests = [compilation, cache_enable, evaluation, introspection].into_iter().flatten();
if let Ok(request) = self.receiver.try_recv() {
match request {
GraphRuntimeRequest::CompilationRequest(CompilationRequest { network, font_cache, editor_metadata }) => {
// Insert the monitor node to manage the inspection
// self.inspect_state = inspect_node.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect));
self.node_graph_errors.clear();
let result = self.update_network(network, font_cache, editor_metadata).await;
self.sender.send_compilation_response(CompilationResponse {
@@ -125,25 +128,20 @@ impl NodeRuntime {
node_graph_errors: self.node_graph_errors.clone(),
});
}
// Inputs to monitor is sent from the editor, and represents a list of input connectors to track the data through
// During the execution. If the value is None, then the node was not evaluated, which can occur due to caching
GraphRuntimeRequest::EvaluationRequest(EvaluationRequest {
evaluation_id,
context,
node_to_evaluate,
nodes_to_introspect,
}) => {
// for (protonode_input, introspect_mode) in &inputs_to_monitor {
// self.executor.set_introspect(*protonode_input, *introspect_mode)
// }
for node in &nodes_to_introspect {
self.executor.cache_first_evaluation(node)
}
let result = self.executor.evaluate_from_node(context, node_to_evaluate).await;
self.sender.send_evaluation_response(EvaluationResponse { evaluation_id, result });
}
// GraphRuntimeRequest::ThumbnailRenderRequest(_) => {}
GraphRuntimeRequest::IntrospectionRequest(nodes) => {
let mut introspected_nodes = Vec::new();
for protonode in nodes {
let introspected_data = match self.executor.introspect(protonode, true) {
for protonode in nodes_to_introspect {
let introspected_data = match self.executor.introspect(protonode) {
Ok(introspected_data) => introspected_data,
Err(e) => {
log::error!("Could not introspect protonode: {:?}, error: {:?}", protonode, e);
@@ -153,7 +151,11 @@ impl NodeRuntime {
introspected_nodes.push((protonode, introspected_data));
}
self.sender.send_introspection_response(IntrospectionResponse(introspected_nodes));
self.sender.send_evaluation_response(EvaluationResponse {
evaluation_id,
result,
introspected_nodes: IntrospectionResponse(introspected_nodes),
});
}
}
}
@@ -170,15 +172,14 @@ impl NodeRuntime {
// Modifies the NodeNetwork so the tagged values are removed and the document nodes with protonode implementations have their protonode ids set
// Needs to return a mapping of absolute input connectors to protonode callers, types for protonodes, and callers for protonodes, add/remove delta for resolved types
let (proto_network, original_locations) = match scoped_network.flatten() {
let (proto_network, original_locations) = match scoped_network.compile() {
Ok(result) => result,
Err(e) => {
log::error!("Error compiling network: {e:?}");
return Err(e);
}
};
let result = match self.executor.update(proto_network).await {
let result: Result<CompilationMetadata, String> = match self.executor.update(proto_network, None).await {
Ok((types_to_add, types_to_remove)) => {
// Used to remove thumbnails from the mapping of SNI to rendered SVG strings on the frontend, which occurs when the SNI is removed
// When native frontend rendering is possible, the strings can just be stored in the network interface for each protonode with the rest of the type metadata
+21 -10
View File
@@ -1,4 +1,5 @@
use super::*;
use std::sync::mpsc::TryRecvError;
use std::sync::mpsc::{Receiver, Sender};
use wasm_bindgen::prelude::*;
@@ -14,12 +15,13 @@ extern "C" {
/// Handles communication with the NodeRuntime, either locally or via Tauri
#[derive(Debug)]
pub struct NodeRuntimeIO {
// Send to
pub busy: bool,
#[cfg(any(not(feature = "tauri"), test))]
sender: Sender<GraphRuntimeRequest>,
#[cfg(all(feature = "tauri", not(test)))]
sender: Sender<NodeGraphUpdate>,
receiver: Receiver<NodeGraphUpdate>,
pub context_receiver: Receiver<(SNI, usize, EditorContext)>,
}
impl Default for NodeRuntimeIO {
@@ -35,11 +37,14 @@ impl NodeRuntimeIO {
{
let (response_sender, response_receiver) = std::sync::mpsc::channel();
let (request_sender, request_receiver) = std::sync::mpsc::channel();
futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)));
let (context_sender, context_receiver) = std::sync::mpsc::channel();
futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender, context_sender)));
Self {
busy: false,
sender: request_sender,
receiver: response_receiver,
context_receiver,
}
}
@@ -49,19 +54,25 @@ impl NodeRuntimeIO {
Self {
sender: response_sender,
receiver: response_receiver,
context_receiver,
}
}
}
#[cfg(test)]
pub fn with_channels(sender: Sender<GraphRuntimeRequest>, receiver: Receiver<NodeGraphUpdate>) -> Self {
Self { sender, receiver }
}
// #[cfg(test)]
// pub fn with_channels(sender: Sender<GraphRuntimeRequest>, receiver: Receiver<NodeGraphUpdate>) -> Self {
// Self { sender, receiver }
// }
/// Sends a message to the NodeRuntime
pub fn send(&self, message: GraphRuntimeRequest) -> Result<(), String> {
pub fn try_send(&mut self, message: GraphRuntimeRequest) -> Result<(), String> {
#[cfg(any(not(feature = "tauri"), test))]
{
self.sender.send(message).map_err(|e| e.to_string())
if !self.busy {
self.busy = true;
self.sender.send(message).map_err(|e| e.to_string())
} else {
Err("Executor busy".to_string())
}
}
#[cfg(all(feature = "tauri", not(test)))]
@@ -76,7 +87,7 @@ impl NodeRuntimeIO {
}
/// Receives any pending updates from the NodeRuntime
pub fn receive(&self) -> impl Iterator<Item = NodeGraphUpdate> + use<'_> {
pub fn receive(&mut self) -> Result<NodeGraphUpdate, TryRecvError> {
// TODO: This introduces extra latency
#[cfg(all(feature = "tauri", not(test)))]
{
@@ -90,7 +101,7 @@ impl NodeRuntimeIO {
}
});
}
self.receiver.try_iter()
self.receiver.try_recv()
}
}