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). /// In addition, these messages do not change any state in the backend (aside from caches).
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::CompileActiveDocument), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::CompileActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::EvaluateActiveDocument), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::EvaluateActiveDocumentWithThumbnails),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::IntrospectActiveDocument), // MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::IntrospectActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh,
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh, PropertiesPanelMessageDiscriminant::Refresh,
))), ))),
@@ -123,13 +126,13 @@ impl Dispatcher {
} }
} }
// Add all messages to the queue if queuing messages (except from the end queue message) // // Add all messages to the queue if queuing messages (except from the end queue message)
if !matches!(message, Message::EndIntrospectionQueue) { // if !matches!(message, Message::EndIntrospectionQueue) {
if self.queueing_introspection_messages { // if self.queueing_introspection_messages {
self.introspection_queue.push(message); // self.introspection_queue.push(message);
return; // return;
} // }
} // }
// Print the message at a verbosity level of `info` // 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); self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
@@ -144,9 +147,10 @@ impl Dispatcher {
Message::EndEvaluationQueue => { Message::EndEvaluationQueue => {
self.queueing_evaluation_messages = false; self.queueing_evaluation_messages = false;
} }
Message::ProcessEvaluationQueue(render_output_metadata) => { Message::ProcessEvaluationQueue(render_output_metadata, introspected_nodes) => {
let update_message = PortfolioMessage::ProcessEvaluationResponse { let update_message = PortfolioMessage::ProcessEvaluationResponse {
evaluation_metadata: render_output_metadata, evaluation_metadata: render_output_metadata,
introspected_nodes,
} }
.into(); .into();
// Update the state with the render output and introspected inputs // 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) // 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)); 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(); 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]); Self::schedule_execution(&mut self.message_queues, true, [clear_message]);
} }
Message::NoOp => {} Message::NoOp => {}
@@ -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::EvaluateActiveDocument); responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
// 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::EvaluateActiveDocument); responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
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::EvaluateActiveDocument); responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
// Update the restart and pause/play buttons // Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
@@ -31,8 +31,8 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
} }
responses.add(Message::StartEvaluationQueue); responses.add(Message::StartEvaluationQueue);
responses.add(DocumentMessage::ZoomCanvasToFitAll); responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(Message::EndEvaluationQueue);
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
responses.add(Message::EndEvaluationQueue);
} }
} }
@@ -1,15 +1,15 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{ 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::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::prelude::*;
use crate::messages::tool::utility_types::HintData; use crate::messages::tool::utility_types::HintData;
use graphene_std::uuid::NodeId;
use graphene_std::raster::color::Color; use graphene_std::raster::color::Color;
use graphene_std::text::Font; use graphene_std::text::Font;
use graphene_std::uuid::{NodeId, SNI};
#[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)]
@@ -128,6 +128,10 @@ pub enum FrontendMessage {
#[serde(rename = "box")] #[serde(rename = "box")]
box_selection: Option<BoxSelection>, box_selection: Option<BoxSelection>,
}, },
UpdateContextDuringEvaluation {
#[serde(rename = "contextDuringEvaluation")]
context_during_evaluation: Vec<(SNI, usize, String)>,
},
UpdateContextMenuInformation { UpdateContextMenuInformation {
#[serde(rename = "contextMenuInformation")] #[serde(rename = "contextMenuInformation")]
context_menu_information: Option<ContextMenuInformation>, context_menu_information: Option<ContextMenuInformation>,
@@ -224,6 +228,10 @@ pub enum FrontendMessage {
#[serde(rename = "setColorChoice")] #[serde(rename = "setColorChoice")]
set_color_choice: Option<String>, set_color_choice: Option<String>,
}, },
UpdateGraphBreadcrumbPath {
#[serde(rename = "breadcrumbPath")]
breadcrumb_path: Vec<NodeId>,
},
UpdateGraphFadeArtwork { UpdateGraphFadeArtwork {
percentage: f64, percentage: f64,
}, },
@@ -263,7 +271,7 @@ pub enum FrontendMessage {
UpdateNodeGraphWires { UpdateNodeGraphWires {
wires: Vec<WirePathUpdate>, wires: Vec<WirePathUpdate>,
}, },
ClearAllNodeGraphWires, ClearAllNodeGraphWirePaths,
UpdateNodeGraphControlBarLayout { UpdateNodeGraphControlBarLayout {
#[serde(rename = "layoutTarget")] #[serde(rename = "layoutTarget")]
layout_target: LayoutTarget, layout_target: LayoutTarget,
@@ -287,7 +295,10 @@ pub enum FrontendMessage {
UpdateThumbnails { UpdateThumbnails {
add: Vec<(NodeId, String)>, add: Vec<(NodeId, String)>,
clear: Vec<NodeId>, clear: Vec<NodeId>,
// remove: Vec<NodeId>, #[serde(rename = "wireSNIUpdates")]
wire_sni_updates: Vec<WireSNIUpdate>,
#[serde(rename = "layerSNIUpdates")]
layer_sni_updates: Vec<FrontendNodeSNIUpdate>,
}, },
UpdateToolOptionsLayout { UpdateToolOptionsLayout {
#[serde(rename = "layoutTarget")] #[serde(rename = "layoutTarget")]
+1 -6
View File
@@ -13,12 +13,7 @@ pub enum Message {
EndEvaluationQueue, 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 // 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)] #[serde(skip)]
ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata), ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata, IntrospectionResponse),
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),
#[child] #[child]
Animation(AnimationMessage), Animation(AnimationMessage),
#[child] #[child]
@@ -442,7 +442,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::EnterNestedNetwork { node_id } => { DocumentMessage::EnterNestedNetwork { node_id } => {
self.breadcrumb_network_path.push(node_id); self.breadcrumb_network_path.push(node_id);
self.selection_network_path.clone_from(&self.breadcrumb_network_path); self.selection_network_path.clone_from(&self.breadcrumb_network_path);
responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::ZoomCanvasToFitAll); responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::SetGridAlignedEdges);
@@ -472,7 +472,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.breadcrumb_network_path.pop(); self.breadcrumb_network_path.pop();
self.selection_network_path.clone_from(&self.breadcrumb_network_path); self.selection_network_path.clone_from(&self.breadcrumb_network_path);
} }
responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::PTZUpdate); responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::SetGridAlignedEdges);
@@ -539,7 +539,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars); responses.add(DocumentMessage::RenderScrollbars);
if opened { if opened {
responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::UnloadWirePaths);
} }
if open { if open {
responses.add(ToolMessage::DeactivateTools); responses.add(ToolMessage::DeactivateTools);
@@ -1424,7 +1424,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
center: Key::Alt, center: Key::Alt,
duplicate: Key::Alt, duplicate: Key::Alt,
})); }));
responses.add(PortfolioMessage::EvaluateActiveDocument); responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
} 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;
@@ -1440,7 +1440,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(NodeGraphMessage::UpdateEdges); responses.add(NodeGraphMessage::UpdateEdges);
responses.add(NodeGraphMessage::UpdateBoxSelection); responses.add(NodeGraphMessage::UpdateBoxSelection);
responses.add(NodeGraphMessage::UpdateImportsExports); responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(NodeGraphMessage::UpdateVisibleNodes);
responses.add(NodeGraphMessage::SendWirePaths);
responses.add(FrontendMessage::UpdateNodeGraphTransform { responses.add(FrontendMessage::UpdateNodeGraphTransform {
transform: Transform { transform: Transform {
scale: transform.matrix2.x_axis.x, scale: transform.matrix2.x_axis.x,
@@ -1909,10 +1910,7 @@ impl DocumentMessageHandler {
responses.add(PortfolioMessage::CompileActiveDocument); responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartEvaluationQueue); responses.add(Message::StartEvaluationQueue);
responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
responses.add(Message::EndEvaluationQueue); responses.add(Message::EndEvaluationQueue);
Some(previous_network) 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 // 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::UnloadWires); responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendWires); responses.add(NodeGraphMessage::SendWirePaths);
responses.add(Message::EndEvaluationQueue); responses.add(Message::EndEvaluationQueue);
Some(previous_network) Some(previous_network)
} }
@@ -2576,7 +2574,7 @@ impl DocumentMessageHandler {
layout: Layout::WidgetLayout(document_bar_layout), layout: Layout::WidgetLayout(document_bar_layout),
layout_target: LayoutTarget::DocumentBar, 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>) { 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."), description: Cow::Borrowed("A default node network you can use to create your own custom nodes."),
properties: None, 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 { DocumentNodeDefinition {
identifier: "Merge", identifier: "Merge",
category: "General", category: "General",
@@ -529,21 +448,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [ nodes: [DocumentNode {
DocumentNode { inputs: vec![NodeInput::scope("editor-api")],
inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), skip_deduplication: true,
skip_deduplication: true, cache_output: true,
..Default::default() ..Default::default()
}, }]
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
..Default::default()
},
]
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(id, node)| (NodeId(id as u64), node)) .map(|(id, node)| (NodeId(id as u64), node))
@@ -556,24 +468,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
output_names: vec!["Image".to_string()], output_names: vec!["Image".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [ node_metadata: [DocumentNodeMetadata {
DocumentNodeMetadata { persistent_metadata: DocumentNodePersistentMetadata {
persistent_metadata: DocumentNodePersistentMetadata { display_name: "Create Canvas".to_string(),
display_name: "Create Canvas".to_string(), node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default() ..Default::default()
}, },
DocumentNodeMetadata { ..Default::default()
persistent_metadata: DocumentNodePersistentMetadata { }]
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(id, node)| (NodeId(id as u64), node)) .map(|(id, node)| (NodeId(id as u64), node))
@@ -595,19 +497,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() cache_output: true,
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@@ -650,14 +547,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
..Default::default() ..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 { DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata { persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Rasterize".to_string(), display_name: "Rasterize".to_string(),
@@ -1408,33 +1297,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(3), 0)], exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: vec![ nodes: vec![DocumentNode {
DocumentNode { inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER), manual_composition: Some(generic!(T)),
manual_composition: Some(generic!(T)), cache_output: true,
..Default::default() ..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() .into_iter()
.enumerate() .enumerate()
.map(|(id, node)| (NodeId(id as u64), node)) .map(|(id, node)| (NodeId(id as u64), node))
@@ -1450,40 +1320,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [ node_metadata: [DocumentNodeMetadata {
DocumentNodeMetadata { persistent_metadata: DocumentNodePersistentMetadata {
persistent_metadata: DocumentNodePersistentMetadata { display_name: "Boolean Operation".to_string(),
display_name: "Boolean Operation".to_string(), node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default() ..Default::default()
}, },
DocumentNodeMetadata { ..Default::default()
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() .into_iter()
.enumerate() .enumerate()
.map(|(id, node)| (NodeId(id as u64), node)) .map(|(id, node)| (NodeId(id as u64), node))
@@ -1506,7 +1350,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(4), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
@@ -1526,24 +1370,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(0), 0),
], ],
implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
manual_composition: Some(generic!(T)), cache_output: true,
..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),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -1585,30 +1412,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
..Default::default() ..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() .into_iter()
.enumerate() .enumerate()
@@ -1671,96 +1474,17 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Vector: Modifier", category: "Vector: Modifier",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { 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![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true), NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
NodeInput::value(TaggedValue::F64(10.), false), NodeInput::network(concrete!(f64), 1),
NodeInput::value(TaggedValue::U32(0), false), NodeInput::network(concrete!(u32), 2),
], ],
cache_output: true,
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { 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![ input_metadata: vec![
("Vector Data", "TODO").into(), ("Vector Data", "TODO").into(),
InputMetadata::with_name_description_override( InputMetadata::with_name_description_override(
@@ -121,8 +121,8 @@ pub enum NodeGraphMessage {
}, },
SendClickTargets, SendClickTargets,
EndSendClickTargets, EndSendClickTargets,
UnloadWires, UnloadWirePaths,
SendWires, SendWirePaths,
UpdateVisibleNodes, UpdateVisibleNodes,
SendGraph, SendGraph,
SetGridAlignedEdges, 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::document_message_handler::navigation_controls;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; 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::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::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; 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::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::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::prelude::*;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; 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 /// The end index of the moved port
end_index: Option<usize>, 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 /// 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 /// 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)>, frontend_wires: HashSet<(NodeId, usize)>,
} }
@@ -918,7 +918,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
wire_in_progress_to_connector, wire_in_progress_to_connector,
from_connector_is_layer, from_connector_is_layer,
to_connector_is_layer, to_connector_is_layer,
GraphWireStyle::Direct, &GraphWireStyle::Direct,
); );
let mut path_string = String::new(); let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); 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, thick: false,
dashed: false, dashed: false,
center: None, center: None,
input_sni: None,
}; };
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) }); responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) });
} }
@@ -1179,7 +1178,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
{ {
return None; 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)) wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
}) })
.collect::<Vec<_>>(); .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)), click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
}), }),
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }), 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) { for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) {
network_interface.unload_wire(&input, breadcrumb_network_path); network_interface.unload_wire(&input, breadcrumb_network_path);
} }
responses.add(FrontendMessage::ClearAllNodeGraphWires); responses.add(FrontendMessage::ClearAllNodeGraphWirePaths);
} }
NodeGraphMessage::SendWires => { NodeGraphMessage::SendWirePaths => {
let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path); let wires = self.collect_wires_paths(network_interface, &preferences.graph_wire_style, breadcrumb_network_path);
responses.add(FrontendMessage::UpdateNodeGraphWires { wires }); responses.add(FrontendMessage::UpdateNodeGraphWires { wires });
} }
NodeGraphMessage::UpdateVisibleNodes => { NodeGraphMessage::UpdateVisibleNodes => {
@@ -1347,8 +1346,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path); let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
self.frontend_nodes = nodes.iter().map(|node| node.id).collect(); self.frontend_nodes = nodes.iter().map(|node| node.id).collect();
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes }); 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); let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
responses.add(NodeGraphMessage::UpdateImportsExports); 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); 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 // Send the new edges to the frontend
responses.add(NodeGraphMessage::UpdateImportsExports); responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(NodeGraphMessage::SendWirePaths);
} }
} }
NodeGraphMessage::SetInputValue { node_id, input_index, value } => { 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) { 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(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartEvaluationQueue); responses.add(Message::StartEvaluationQueue);
responses.add(NodeGraphMessage::SendWires); responses.add(NodeGraphMessage::SendWirePaths);
responses.add(Message::EndEvaluationQueue); 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 => { NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { 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 } => { NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path); 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 } => { NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) { 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(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::SendWires); responses.add(NodeGraphMessage::SendWirePaths);
} }
NodeGraphMessage::SetDisplayName { NodeGraphMessage::SetDisplayName {
node_id, 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 let mut added_wires = network_interface
.node_graph_input_connectors(breadcrumb_network_path) .node_graph_input_connectors(breadcrumb_network_path)
.iter() .iter()
@@ -2278,18 +2313,24 @@ impl NodeGraphMessageHandler {
let locked = network_interface.is_locked(&node_id, breadcrumb_network_path); 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 let errors: Option<String> = self
self.node_graph_errors .node_graph_errors
.iter() .iter()
.find(|error| match &error.original_location { .find(|error| match &error.original_location {
graph_craft::proto::OriginalLocation::Value(_) => false, 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())) .map(|error| format!("{:?}", error.error.clone()))
.or_else(|| { .or_else(|| {
if self.node_graph_errors.iter().any(|error| match &error.original_location { if self.node_graph_errors.iter().any(|error| match &error.original_location {
graph_craft::proto::OriginalLocation::Value(_) => false, 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()) Some("Node graph type error within this node".to_string())
} else { } 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 { nodes.push(FrontendNode {
id: node_id, id: node_id,
is_layer: network_interface is_layer,
.node_metadata(&node_id, breadcrumb_network_path) layer_thumbnail_sni: is_layer
.is_some_and(|node_metadata| node_metadata.persistent_metadata.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), can_be_layer: can_be_layer_lookup.contains(&node_id),
reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(), reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(),
display_name: network_interface.display_name(&node_id, breadcrumb_network_path), 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 crate::messages::portfolio::document::utility_types::network_interface::TypeSource;
use graph_craft::document::{InputConnector, OutputConnector};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graphene_std::uuid::NodeId; use graph_craft::document::{InputConnector, OutputConnector};
use graphene_std::Type; use graphene_std::Type;
use graphene_std::uuid::NodeId;
use std::borrow::Cow; use std::borrow::Cow;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -76,6 +76,8 @@ pub struct FrontendNode {
pub id: NodeId, pub id: NodeId,
#[serde(rename = "isLayer")] #[serde(rename = "isLayer")]
pub is_layer: bool, pub is_layer: bool,
#[serde(rename = "layerThumbnailSNI")]
pub layer_thumbnail_sni: Option<NodeId>,
#[serde(rename = "canBeLayer")] #[serde(rename = "canBeLayer")]
pub can_be_layer: bool, pub can_be_layer: bool,
pub reference: Option<String>, pub reference: Option<String>,
@@ -98,6 +100,12 @@ pub struct FrontendNode {
pub ui_only: bool, 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)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType { pub struct FrontendNodeType {
pub name: Cow<'static, str>, 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 }) .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> { pub fn viewport_loaded_thumbnail_position(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option<DVec2> {
if !self.wire_is_loaded(input, network_path) { let wire = self.wire_path_from_input(input, graph_wire_style, false, network_path)?;
self.load_wire(input, graph_wire_style, network_path); let network_metadata = self.network_metadata(network_path)?;
} else { if wire.thick {
return None; 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 } => { InputConnector::Node { node_id, input_index } => {
let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?; let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?;
let TransientMetadata::Loaded(wire) = &input_metadata.wire else { let TransientMetadata::Loaded(wire) = &input_metadata.wire else {
log::error!("Could not load wire for input: {:?}", input);
return None; return None;
}; };
wire.clone() Some(wire)
} }
InputConnector::Export(export_index) => { InputConnector::Export(export_index) => {
let network_metadata = self.network_metadata(network_path)?; let network_metadata = self.network_metadata(network_path)?;
let Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else { 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; return None;
}; };
wire.clone() Some(wire)
}
};
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,
}
} }
} }
} }
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) { let dashed = match self.previewing(network_path) {
Previewing::Yes { .. } => match input { Previewing::Yes { .. } => match input {
InputConnector::Node { .. } => false, InputConnector::Node { .. } => false,
@@ -2698,7 +2690,7 @@ impl NodeNetworkInterface {
} }
/// When previewing, there may be a second path to the root node. /// 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 input = InputConnector::Export(0);
let current_export = self.upstream_output_connector(&input, network_path)?; let current_export = self.upstream_output_connector(&input, network_path)?;
@@ -2733,7 +2725,6 @@ impl NodeNetworkInterface {
thick, thick,
dashed: false, dashed: false,
center: None, center: None,
input_sni: None,
}); });
Some(WirePathUpdate { Some(WirePathUpdate {
@@ -2744,7 +2735,7 @@ impl NodeNetworkInterface {
} }
/// Returns the vector subpath and a boolean of whether the wire should be thick. /// 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 { let Some(input_position) = self.get_input_center(input, network_path) else {
log::error!("Could not get dom rect for wire end: {:?}", input); log::error!("Could not get dom rect for wire end: {:?}", input);
return None; 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_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 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 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)) 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 (vector_wire, thick, center) = self.vector_wire_from_input(input, graph_wire_style, network_path)?;
let mut path_string = String::new(); let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); 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 data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0);
let input_sni = self.protonode_from_input(input, network_path);
Some(WirePath { Some(WirePath {
path_string, path_string,
data_type, data_type,
thick, thick,
dashed, dashed,
center: Some((center.x, center.y)), center: Some((center.x as i32, center.y as i32)),
input_sni,
}) })
} }
@@ -12,9 +12,7 @@ pub struct WirePath {
pub data_type: FrontendGraphDataType, pub data_type: FrontendGraphDataType,
pub thick: bool, pub thick: bool,
pub dashed: bool, pub dashed: bool,
pub center: Option<(f64, f64)>, pub center: Option<(i32, i32)>,
#[serde(rename = "inputSni")]
pub input_sni: Option<NodeId>,
} }
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -27,6 +25,14 @@ pub struct WirePathUpdate {
pub wire_path_update: Option<WirePath>, 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)] #[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum GraphWireStyle { pub enum GraphWireStyle {
#[default] #[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.; let grid_spacing = 24.;
match graph_wire_style { match graph_wire_style {
GraphWireStyle::Direct => { GraphWireStyle::Direct => {
@@ -394,14 +394,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::transform_nodes::transform::IDENTIFIER, node: graphene_std::transform_nodes::transform::IDENTIFIER,
aliases: &["graphene_core::transform::TransformNode"], 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 { NodeReplacement {
node: graphene_std::vector::spline::IDENTIFIER, node: graphene_std::vector::spline::IDENTIFIER,
@@ -9,7 +9,7 @@ use graphene_std::Color;
use graphene_std::raster::Image; use graphene_std::raster::Image;
use graphene_std::renderer::RenderMetadata; use graphene_std::renderer::RenderMetadata;
use graphene_std::text::Font; use graphene_std::text::Font;
use graphene_std::uuid::{SNI}; use graphene_std::uuid::SNI;
#[impl_message(Message, Portfolio)] #[impl_message(Message, Portfolio)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -22,17 +22,18 @@ pub enum PortfolioMessage {
#[child] #[child]
Spreadsheet(SpreadsheetMessage), 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 // Sends a request to compile the network. Should occur when any value, preference, or font changes
CompileActiveDocument, CompileActiveDocument,
// Sends a request to evaluate the network. Should occur when any context value changes. // Sends a request to evaluate the network. Should occur when any context value changes.
EvaluateActiveDocument, // Nodes to introspect is in addition to all nodes to render thumbnails for
// Sends a request to introspect data in the network, and return it to the editor EvaluateActiveDocumentWithThumbnails,
IntrospectActiveDocument { EvaluateActiveDocument {
nodes_to_introspect: HashSet<SNI>, 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 { ExportActiveDocument {
file_name: String, file_name: String,
file_type: FileType, file_type: FileType,
@@ -47,12 +48,11 @@ pub enum PortfolioMessage {
}, },
ProcessEvaluationResponse { ProcessEvaluationResponse {
evaluation_metadata: RenderMetadata, evaluation_metadata: RenderMetadata,
},
ProcessIntrospectionResponse {
#[serde(skip)] #[serde(skip)]
introspection_response: IntrospectionResponse, introspected_nodes: IntrospectionResponse,
}, },
RenderThumbnails, // Introspected data is cleared after queued messages are complete
ClearIntrospectedData,
ProcessThumbnails, ProcessThumbnails,
DocumentPassMessage { DocumentPassMessage {
document_id: DocumentId, 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::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::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; 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::document_migration::*;
use crate::messages::portfolio::spreadsheet::SpreadsheetMessageHandlerData; use crate::messages::portfolio::spreadsheet::SpreadsheetMessageHandlerData;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
@@ -21,8 +22,9 @@ use crate::node_graph_executor::{CompilationRequest, ExportConfig, NodeGraphExec
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::document::value::EditorMetadata; use graph_craft::document::value::EditorMetadata;
use graph_craft::document::{InputConnector, NodeInput, OutputConnector}; 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::application_io::TimingInformation;
use graphene_std::memo::MonitorIntrospectResult;
use graphene_std::renderer::{Quad, RenderMetadata}; use graphene_std::renderer::{Quad, RenderMetadata};
use graphene_std::text::Font; use graphene_std::text::Font;
use graphene_std::transform::{Footprint, RenderQuality}; use graphene_std::transform::{Footprint, RenderQuality};
@@ -57,10 +59,10 @@ pub struct PortfolioMessageHandler {
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, which is populated after an introspection request. // Data from the node graph, which is populated after an introspection request.
// To access the data, schedule messages with StartIntrospectionQueue [messages] EndIntrospectionQueue // To access the data, schedule messages with StartEvaluationQueue [messages] EndEvaluationQueue
// The data is no longer accessible after EndIntrospectionQueue // The data is no longer accessible after EndEvaluationQueue
pub introspected_data: HashMap<SNI, Option<Arc<dyn std::any::Any + Send + Sync>>>, pub introspected_data: HashMap<SNI, MonitorIntrospectResult>,
pub previous_thumbnail_data: HashMap<SNI, Arc<dyn std::any::Any + Send + Sync>>, thumbnails_to_clear: Vec<SNI>,
} }
#[message_handler_data] #[message_handler_data]
@@ -791,8 +793,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
transform_to_viewport: true, transform_to_viewport: true,
}, },
}); });
// Also evaluate the document after compilation
responses.add_front(PortfolioMessage::EvaluateActiveDocument);
} }
} }
PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => { PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => {
@@ -814,67 +814,23 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// Remove all thumbnails // Remove all thumbnails
cleared_thumbnails.push(sni); cleared_thumbnails.push(sni);
} }
responses.add(FrontendMessage::UpdateThumbnails {
add: Vec::new(), self.thumbnails_to_clear.extend(cleared_thumbnails);
clear: 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 { 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); log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return; 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 { // let animation_time = match animation.timing_information().animation_time {
// AnimationState::Stopped => 0., // AnimationState::Stopped => 0.,
// AnimationState::Playing { start } => ipp.time - start, // AnimationState::Playing { start } => ipp.time - start,
@@ -891,9 +847,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
context.real_time = Some(ipp.time); context.real_time = Some(ipp.time);
context.downstream_transform = Some(DAffine2::IDENTITY); 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 { let RenderMetadata {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
@@ -915,104 +880,71 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// AnimationState::Playing { .. } => responses.add(PortfolioMessage::EvaluateActiveDocument), // 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::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 { 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); log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return; return;
}; };
// All possible inputs, later check if they are connected to any nodes let mut wire_sni_updates = document
let mut nodes_to_render = HashSet::new(); .network_interface
.document_metadata()
// Get all inputs to render thumbnails for .all_layers()
// Get all protonodes for all connected side layer inputs connected to the export in the document network .map(|layer| WireSNIUpdate {
for layer in document.network_interface.document_metadata().all_layers() { id: layer.to_node(),
let connector = InputConnector::Node {
node_id: layer.to_node(),
input_index: 1, input_index: 1,
}; sni: document.network_interface.protonode_from_input(&InputConnector::node(layer.to_node(), 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, &[]) { .collect::<Vec<_>>();
nodes_to_render.insert(compiled_input); 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 { if document.graph_view_overlay_open {
let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else { let (layer_updates, wire_updates) = NodeGraphMessageHandler::graph_sni_updates(&document.network_interface, &document.breadcrumb_network_path);
return; layer_sni_updates.extend(layer_updates);
}; wire_sni_updates.extend(wire_updates);
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 mut clear = std::mem::take(&mut self.thumbnails_to_clear);
clear.extend(thumbnail_response.clear);
responses.add(FrontendMessage::UpdateThumbnails { responses.add(FrontendMessage::UpdateThumbnails {
add: thumbnail_response.add, add: thumbnail_response.add,
clear: thumbnail_response.clear, clear,
}) wire_sni_updates,
layer_sni_updates,
});
} }
PortfolioMessage::ExportActiveDocument { PortfolioMessage::ExportActiveDocument {
file_name, file_name,
@@ -1081,6 +1013,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
transparent_background, transparent_background,
size: scaled_size, size: scaled_size,
}), }),
HashSet::new(),
); );
// if let Some((start, end, fps)) = animation_export_data { // if let Some((start, end, fps)) = animation_export_data {
@@ -1300,11 +1233,100 @@ 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::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 }); responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
} }
result 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)] #[derive(Clone, Debug, Default)]
@@ -7,6 +7,7 @@ use graph_craft::document::OutputConnector;
use graphene_std::Color; use graphene_std::Color;
use graphene_std::GraphicGroupTable; use graphene_std::GraphicGroupTable;
use graphene_std::instances::Instances; use graphene_std::instances::Instances;
use graphene_std::memo::MonitorIntrospectResult;
use graphene_std::raster::Image; use graphene_std::raster::Image;
use graphene_std::uuid::{NodeId, SNI}; use graphene_std::uuid::{NodeId, SNI};
use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::vector::{VectorData, VectorDataTable};
@@ -15,7 +16,7 @@ use std::sync::Arc;
#[derive(ExtractField)] #[derive(ExtractField)]
pub struct SpreadsheetMessageHandlerData<'a> { 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 // Network interface of the selected document
pub network_interface: &'a NodeNetworkInterface, pub network_interface: &'a NodeNetworkInterface,
} }
@@ -26,7 +27,7 @@ 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,
// Path to the document node that is introspected. The protonode is found by traversing from the primary output // 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>, node_to_inspect: Option<NodeId>,
instances_path: Vec<usize>, instances_path: Vec<usize>,
@@ -73,10 +74,10 @@ impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData<'_>> for S
let mut nodes_to_introspect = HashSet::new(); let mut nodes_to_introspect = HashSet::new();
nodes_to_introspect.insert(protonode_id); nodes_to_introspect.insert(protonode_id);
responses.add(PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect }); responses.add(PortfolioMessage::EvaluateActiveDocument { nodes_to_introspect });
responses.add(Message::StartIntrospectionQueue); responses.add(Message::StartEvaluationQueue);
responses.add(SpreadsheetMessage::ProcessUpdateLayout { node_to_inspect, protonode_id }); responses.add(SpreadsheetMessage::ProcessUpdateLayout { node_to_inspect, protonode_id });
responses.add(Message::EndIntrospectionQueue); responses.add(Message::EndEvaluationQueue);
self.update_layout(responses); self.update_layout(responses);
} }
@@ -93,7 +94,6 @@ impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData<'_>> for S
self.instances_path.truncate(len); self.instances_path.truncate(len);
self.update_layout(responses); self.update_layout(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(responses);
@@ -126,11 +126,13 @@ impl SpreadsheetMessageHandler {
Some(_) => { Some(_) => {
match &self.inspection_data { match &self.inspection_data {
Some(data) => match 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, Some(layout) => layout,
None => label("The introspected data is not a supported type to be displayed."), 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 // 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"), None => label("The output of this node could not be determined"),
@@ -82,8 +82,8 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
} }
PreferencesMessage::GraphWireStyle { style } => { PreferencesMessage::GraphWireStyle { style } => {
self.graph_wire_style = style; self.graph_wire_style = style;
responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::UnloadWirePaths);
responses.add(NodeGraphMessage::SendWires); responses.add(NodeGraphMessage::SendWirePaths);
} }
PreferencesMessage::ViewportZoomWheelRate { rate } => { PreferencesMessage::ViewportZoomWheelRate { rate } => {
self.viewport_zoom_wheel_rate = 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::value::{EditorMetadata, RenderOutput, TaggedValue};
use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid}; use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid};
use graph_craft::proto::GraphErrors; 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::renderer::format_transform_matrix;
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::uuid::SNI; use graphene_std::uuid::SNI;
@@ -30,6 +31,11 @@ pub struct CompilationResponse {
node_graph_errors: GraphErrors, 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 // Metadata the editor sends when evaluating the network
#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
pub struct EvaluationRequest { pub struct EvaluationRequest {
@@ -37,16 +43,18 @@ pub struct EvaluationRequest {
#[serde(skip)] #[serde(skip)]
pub context: EditorContext, pub context: EditorContext,
pub node_to_evaluate: Option<SNI>, pub node_to_evaluate: Option<SNI>,
pub nodes_to_introspect: HashSet<SNI>,
} }
// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] // #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub struct EvaluationResponse { pub struct EvaluationResponse {
evaluation_id: u64, evaluation_id: u64,
result: Result<TaggedValue, String>, result: Result<TaggedValue, String>,
introspected_nodes: IntrospectionResponse,
} }
#[derive(Debug, Clone, Default)] #[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 { impl PartialEq for IntrospectionResponse {
fn eq(&self, _other: &Self) -> bool { fn eq(&self, _other: &Self) -> bool {
@@ -58,7 +66,6 @@ impl PartialEq for IntrospectionResponse {
pub enum NodeGraphUpdate { pub enum NodeGraphUpdate {
CompilationResponse(CompilationResponse), CompilationResponse(CompilationResponse),
EvaluationResponse(EvaluationResponse), EvaluationResponse(EvaluationResponse),
IntrospectionResponse(IntrospectionResponse),
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@@ -83,18 +90,18 @@ impl Default for NodeGraphExecutor {
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)]
pub(crate) fn new_with_local_runtime() -> (NodeRuntime, Self) { // pub(crate) fn new_with_local_runtime() -> (NodeRuntime, Self) {
let (request_sender, request_receiver) = std::sync::mpsc::channel(); // let (request_sender, request_receiver) = std::sync::mpsc::channel();
let (response_sender, response_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_runtime = NodeRuntime::new(request_receiver, response_sender);
let node_executor = Self { // let node_executor = Self {
futures: HashMap::new(), // futures: HashMap::new(),
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver), // runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
}; // };
(node_runtime, node_executor) // (node_runtime, node_executor)
} // }
/// 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)]
@@ -110,19 +117,28 @@ impl NodeGraphExecutor {
/// Compile the network /// Compile the network
pub fn submit_node_graph_compilation(&mut self, compilation_request: CompilationRequest) { 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); log::error!("Could not send evaluation request. {:?}", error);
return; 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. /// 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(); 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, evaluation_id,
context, context,
node_to_evaluate, node_to_evaluate,
nodes_to_introspect,
})) { })) {
log::error!("Could not send evaluation request. {:?}", error); log::error!("Could not send evaluation request. {:?}", error);
return; return;
@@ -131,18 +147,43 @@ impl NodeGraphExecutor {
self.futures.insert(evaluation_id, evaluation_context); self.futures.insert(evaluation_id, evaluation_context);
} }
pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet<SNI>) { // 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)) { // if let Err(error) = self.runtime_io.try_send(GraphRuntimeRequest::IntrospectionRequest(nodes_to_introspect)) {
log::error!("Could not send evaluation request. {:?}", error); // log::error!("Could not send evaluation request. {:?}", error);
return; // return;
} // }
} // }
// Continuously poll the executor (called by request animation frame) // 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> {
for response in self.runtime_io.receive() { if let Ok(response) = self.runtime_io.receive() {
self.runtime_io.busy = false;
match response { 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); responses.add(OverlaysMessage::Draw);
let node_graph_output = match result { let node_graph_output = match result {
@@ -186,36 +227,17 @@ impl NodeGraphExecutor {
} }
} else { } else {
// Update artwork // Update artwork
self.process_node_graph_output(render_output, responses)? self.process_node_graph_output(render_output, introspected_nodes, responses)?
} }
} } // NodeGraphUpdate::IntrospectionResponse(introspection_response) => {
NodeGraphUpdate::CompilationResponse(compilation_response) => { // responses.add_front(Message::ProcessIntrospectionQueue(introspection_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));
}
} }
} }
Ok(()) 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 { match render_output.data {
graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => { graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => {
// Send to frontend // Send to frontend
@@ -234,7 +256,16 @@ impl NodeGraphExecutor {
return Err(format!("Invalid node graph output type: {:#?}", render_output.data)); 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(()) Ok(())
} }
} }
+34 -33
View File
@@ -24,6 +24,7 @@ pub struct NodeRuntime {
executor: DynamicExecutor, executor: DynamicExecutor,
receiver: Receiver<GraphRuntimeRequest>, receiver: Receiver<GraphRuntimeRequest>,
sender: NodeGraphRuntimeSender, sender: NodeGraphRuntimeSender,
context_sender: Sender<(SNI, usize, EditorContext)>,
application_io: Option<Arc<WasmApplicationIo>>, application_io: Option<Arc<WasmApplicationIo>>,
@@ -46,7 +47,6 @@ pub enum GraphRuntimeRequest {
// ThumbnailRenderRequest(HashSet<CompiledProtonodeInput>), // 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. // 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 // Can also be used by the spreadsheet/introspection system
IntrospectionRequest(HashSet<SNI>),
} }
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -69,19 +69,20 @@ impl NodeGraphRuntimeSender {
fn send_evaluation_response(&self, response: EvaluationResponse) { fn send_evaluation_response(&self, response: EvaluationResponse) {
self.0.send(NodeGraphUpdate::EvaluationResponse(response)).expect("Failed to send evaluation response") self.0.send(NodeGraphUpdate::EvaluationResponse(response)).expect("Failed to send evaluation response")
} }
fn send_introspection_response(&self, response: IntrospectionResponse) { // fn send_introspection_response(&self, response: IntrospectionResponse) {
self.0.send(NodeGraphUpdate::IntrospectionResponse(response)).expect("Failed to send introspection response") // 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)); pub static NODE_RUNTIME: Lazy<Mutex<Option<NodeRuntime>>> = Lazy::new(|| Mutex::new(None));
impl NodeRuntime { 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 { Self {
executor: DynamicExecutor::default(), executor: DynamicExecutor::default(),
receiver, receiver,
sender: NodeGraphRuntimeSender(sender.clone()), sender: NodeGraphRuntimeSender(sender.clone()),
context_sender,
application_io: None, application_io: None,
@@ -100,24 +101,26 @@ impl NodeRuntime {
} }
// TODO: This deduplication of messages may cause issues // TODO: This deduplication of messages may cause issues
let mut compilation = None; // let mut compilation = None;
let mut evaluation = None; // let mut cache_enable = None;
let mut introspection = None; // let mut evaluation = None;
for request in self.receiver.try_iter() { // let mut introspection = None;
match request { // for request in self.receiver.try_iter() {
GraphRuntimeRequest::CompilationRequest(_) => compilation = Some(request), // match request {
GraphRuntimeRequest::EvaluationRequest(_) => evaluation = Some(request), // GraphRuntimeRequest::CompilationRequest(_) => compilation = Some(request),
GraphRuntimeRequest::IntrospectionRequest(_) => introspection = Some(request), // GraphRuntimeRequest::CacheEnableRequest(_) => cache_enable = Some(request),
} // GraphRuntimeRequest::EvaluationRequest(_) => evaluation = Some(request),
} // GraphRuntimeRequest::IntrospectionRequest(_) => introspection = Some(request),
let requests = [compilation, evaluation, introspection].into_iter().flatten(); // }
// }
for request in requests { // let requests = [compilation, cache_enable, evaluation, introspection].into_iter().flatten();
if let Ok(request) = self.receiver.try_recv() {
match request { match request {
GraphRuntimeRequest::CompilationRequest(CompilationRequest { network, font_cache, editor_metadata }) => { GraphRuntimeRequest::CompilationRequest(CompilationRequest { network, font_cache, editor_metadata }) => {
// 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.node_graph_errors.clear(); self.node_graph_errors.clear();
let result = self.update_network(network, font_cache, editor_metadata).await; let result = self.update_network(network, font_cache, editor_metadata).await;
self.sender.send_compilation_response(CompilationResponse { self.sender.send_compilation_response(CompilationResponse {
@@ -125,25 +128,20 @@ impl NodeRuntime {
node_graph_errors: self.node_graph_errors.clone(), 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 { GraphRuntimeRequest::EvaluationRequest(EvaluationRequest {
evaluation_id, evaluation_id,
context, context,
node_to_evaluate, node_to_evaluate,
nodes_to_introspect,
}) => { }) => {
// for (protonode_input, introspect_mode) in &inputs_to_monitor { for node in &nodes_to_introspect {
// self.executor.set_introspect(*protonode_input, *introspect_mode) self.executor.cache_first_evaluation(node)
// } }
let result = self.executor.evaluate_from_node(context, node_to_evaluate).await; 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(); let mut introspected_nodes = Vec::new();
for protonode in nodes { for protonode in nodes_to_introspect {
let introspected_data = match self.executor.introspect(protonode, true) { let introspected_data = match self.executor.introspect(protonode) {
Ok(introspected_data) => introspected_data, Ok(introspected_data) => introspected_data,
Err(e) => { Err(e) => {
log::error!("Could not introspect protonode: {:?}, error: {:?}", protonode, e); log::error!("Could not introspect protonode: {:?}, error: {:?}", protonode, e);
@@ -153,7 +151,11 @@ impl NodeRuntime {
introspected_nodes.push((protonode, introspected_data)); 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 // 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 // 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, Ok(result) => result,
Err(e) => { Err(e) => {
log::error!("Error compiling network: {e:?}"); log::error!("Error compiling network: {e:?}");
return Err(e); return Err(e);
} }
}; };
let result: Result<CompilationMetadata, String> = match self.executor.update(proto_network, None).await {
let result = match self.executor.update(proto_network).await {
Ok((types_to_add, types_to_remove)) => { 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 // 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 // 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 super::*;
use std::sync::mpsc::TryRecvError;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@@ -14,12 +15,13 @@ extern "C" {
/// Handles communication with the NodeRuntime, either locally or via Tauri /// Handles communication with the NodeRuntime, either locally or via Tauri
#[derive(Debug)] #[derive(Debug)]
pub struct NodeRuntimeIO { pub struct NodeRuntimeIO {
// Send to pub busy: bool,
#[cfg(any(not(feature = "tauri"), test))] #[cfg(any(not(feature = "tauri"), test))]
sender: Sender<GraphRuntimeRequest>, sender: Sender<GraphRuntimeRequest>,
#[cfg(all(feature = "tauri", not(test)))] #[cfg(all(feature = "tauri", not(test)))]
sender: Sender<NodeGraphUpdate>, sender: Sender<NodeGraphUpdate>,
receiver: Receiver<NodeGraphUpdate>, receiver: Receiver<NodeGraphUpdate>,
pub context_receiver: Receiver<(SNI, usize, EditorContext)>,
} }
impl Default for NodeRuntimeIO { impl Default for NodeRuntimeIO {
@@ -35,11 +37,14 @@ impl NodeRuntimeIO {
{ {
let (response_sender, response_receiver) = std::sync::mpsc::channel(); let (response_sender, response_receiver) = std::sync::mpsc::channel();
let (request_sender, request_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 { Self {
busy: false,
sender: request_sender, sender: request_sender,
receiver: response_receiver, receiver: response_receiver,
context_receiver,
} }
} }
@@ -49,19 +54,25 @@ impl NodeRuntimeIO {
Self { Self {
sender: response_sender, sender: response_sender,
receiver: response_receiver, receiver: response_receiver,
context_receiver,
} }
} }
} }
#[cfg(test)] // #[cfg(test)]
pub fn with_channels(sender: Sender<GraphRuntimeRequest>, receiver: Receiver<NodeGraphUpdate>) -> Self { // pub fn with_channels(sender: Sender<GraphRuntimeRequest>, receiver: Receiver<NodeGraphUpdate>) -> Self {
Self { sender, receiver } // Self { sender, receiver }
} // }
/// Sends a message to the NodeRuntime /// 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))] #[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)))] #[cfg(all(feature = "tauri", not(test)))]
@@ -76,7 +87,7 @@ impl NodeRuntimeIO {
} }
/// Receives any pending updates from the NodeRuntime /// 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 // TODO: This introduces extra latency
#[cfg(all(feature = "tauri", not(test)))] #[cfg(all(feature = "tauri", not(test)))]
{ {
@@ -90,7 +101,7 @@ impl NodeRuntimeIO {
} }
}); });
} }
self.receiver.try_iter() self.receiver.try_recv()
} }
} }
+1 -1
View File
@@ -79,6 +79,6 @@ fn runtime_message(message: String) -> Result<(), String> {
return Err("Failed to deserialize message".into()); return Err("Failed to deserialize message".into());
} }
}; };
let response = NODE_RUNTIME_IO.lock().as_ref().unwrap().as_ref().unwrap().send(message); let response = NODE_RUNTIME_IO.lock().as_ref().unwrap().as_ref().unwrap().try_send(message);
response response
} }
+8 -2
View File
@@ -92,6 +92,12 @@
updateLayerInTree(targetId, targetLayer); updateLayerInTree(targetId, targetLayer);
}); });
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerDetails, (updateDocumentLayerDetails) => {
const targetLayer = updateDocumentLayerDetails.data;
const targetId = targetLayer.id;
updateLayerInTree(targetId, targetLayer);
});
addEventListener("pointermove", clippingHover); addEventListener("pointermove", clippingHover);
addEventListener("keydown", clippingKeyPress); addEventListener("keydown", clippingKeyPress);
addEventListener("keyup", clippingKeyPress); addEventListener("keyup", clippingKeyPress);
@@ -540,8 +546,8 @@
<IconLabel icon="Clipped" class="clipped-arrow" tooltip={"Clipping mask is active (Alt-click border to release)"} /> <IconLabel icon="Clipped" class="clipped-arrow" tooltip={"Clipping mask is active (Alt-click border to release)"} />
{/if} {/if}
<div class="thumbnail"> <div class="thumbnail">
{#if $nodeGraph.thumbnails.has(listing.entry.id)} {#if $nodeGraph.thumbnails.get($nodeGraph.wires.get(listing.entry.id)?.get(BigInt(1))?.sni) !== undefined}
{@html $nodeGraph.thumbnails.get(listing.entry.id)} {@html $nodeGraph.thumbnails.get($nodeGraph.wires.get(listing.entry.id)?.get(BigInt(1))?.sni)}
{/if} {/if}
</div> </div>
{#if listing.entry.name === "Artboard"} {#if listing.entry.name === "Artboard"}
+59 -14
View File
@@ -230,6 +230,22 @@
} }
return result; return result;
} }
function generateCheckerboardString() {
let rects = "";
for (let y = 0; y < 6; y++) {
for (let x = 0; x < 8; x++) {
const isDark = (x + y) % 2 === 0;
const fill = isDark ? "#cccccc" : "#ffffff";
rects += `<rect x="${x * 4 - 16}" y="${y * 4 - 12}" width="${4.1}" height="${4.1}" fill="${fill}" />`;
}
}
rects += ``;
return rects;
}
</script> </script>
<div <div
@@ -317,10 +333,11 @@
<div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}> <div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
<svg> <svg>
{#each $nodeGraph.wires.values() as map} {#each $nodeGraph.wires.values() as map}
{#each map.values() as { pathString, dataType, thick, dashed }} {#each map.values() as { pathString, dataType, thick, dashed, center, sni }}
{#if thick} {#if thick}
<path <path
d={pathString} d={pathString}
class="wire-path"
style:--data-line-width={"8px"} style:--data-line-width={"8px"}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`} style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
@@ -513,8 +530,8 @@
<span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span> <span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
{/if} {/if}
<div class="thumbnail"> <div class="thumbnail">
{#if $nodeGraph.thumbnails.has(node.id)} {#if node.layerThumbnailSNI && $nodeGraph.thumbnails.has(node.layerThumbnailSNI)}
{@html $nodeGraph.thumbnails.get(node.id)} {@html $nodeGraph.thumbnails.get(node.layerThumbnailSNI)}
{/if} {/if}
<!-- Layer stacking top output --> <!-- Layer stacking top output -->
{#if node.primaryOutput} {#if node.primaryOutput}
@@ -613,8 +630,8 @@
<div class="wires"> <div class="wires">
<svg> <svg>
{#each $nodeGraph.wires.values() as map} {#each $nodeGraph.wires.values() as map}
{#each map.values() as { pathString, dataType, thick, dashed, center, monitorSni }} {#each map.values() as { pathString, dataType, thick, dashed, center, sni }}
{#if !thick} {#if !thick && pathString}
<path <path
d={pathString} d={pathString}
class="wire-path" class="wire-path"
@@ -623,20 +640,16 @@
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`} style:--data-dasharray={`3,${dashed ? 2 : 0}`}
/> />
{/if} <!-- <g transform={`translate(${center[0] - 30}, ${center[1] - 10})`}>
<!-- {console.log("center: ", center)} --> <text fill="white">{sni}</text>
<!-- {console.log("monitorSni: ", monitorSni)} --> </g> -->
{#if center && monitorSni}
<!-- {console.log("thumbnails: ", $nodeGraph.thumbnails.get(monitorSni))} -->
<g transform={`translate(${center[0]}, ${center[1]})`}>
{@html $nodeGraph.thumbnails.get(monitorSni)}
</g>
{/if} {/if}
{/each} {/each}
{/each} {/each}
{#if $nodeGraph.wirePathInProgress} {#if $nodeGraph.wirePathInProgress}
<path <path
d={$nodeGraph.wirePathInProgress?.pathString} d={$nodeGraph.wirePathInProgress?.pathString}
class="wire-path"
style:--data-line-width={`${$nodeGraph.wirePathInProgress.thick ? 8 : 2}px`} style:--data-line-width={`${$nodeGraph.wirePathInProgress.thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()})`} style:--data-color={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()}-dim)`}
@@ -644,6 +657,22 @@
/> />
{/if} {/if}
</svg> </svg>
{#each $nodeGraph.wires.values() as map}
{#each map.values() as { pathString, center, sni, dataType, thick }}
{#if !thick && pathString && sni && center !== undefined}
<div
class="wire-thumbnail"
style:--offset-left-px={`${center[0] - 12}px`}
style:--offset-top-px={`${center[1] - 8}px`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
>
{#if sni && $nodeGraph.thumbnails.has(sni)}
{@html $nodeGraph.thumbnails.get(sni)}
{/if}
</div>
{/if}
{/each}
{/each}
</div> </div>
<!-- Nodes --> <!-- Nodes -->
@@ -891,7 +920,6 @@
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
svg { svg {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -904,6 +932,23 @@
stroke-dasharray: var(--data-dasharray); stroke-dasharray: var(--data-dasharray);
} }
} }
.wire-thumbnail {
// background: var(--color-2-mildblack);
border: 1px solid var(--data-color-dim);
border-radius: 2px;
position: absolute;
left: var(--offset-left-px);
top: var(--offset-top-px);
box-sizing: border-box;
width: 24px;
height: 16px;
// background-color: white;
background-image: var(--color-transparent-checkered-background);
background-size: var(--color-transparent-checkered-background-size-mini);
background-position: var(--color-transparent-checkered-background-position-mini);
background-repeat: var(--color-transparent-checkered-background-repeat);
}
} }
.imports-and-exports { .imports-and-exports {
+34 -6
View File
@@ -12,6 +12,7 @@ export class JsMessage {
} }
const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] })); const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] }));
const ImportsToVec2Array = Transform(({ obj: { imports } }: { obj: { imports: [FrontendGraphOutput, number, number][] } }) => const ImportsToVec2Array = Transform(({ obj: { imports } }: { obj: { imports: [FrontendGraphOutput, number, number][] } }) =>
imports.map(([outputMetadata, x, y]) => ({ outputMetadata, position: { x, y } })), imports.map(([outputMetadata, x, y]) => ({ outputMetadata, position: { x, y } })),
); );
@@ -36,6 +37,10 @@ export class UpdateBox extends JsMessage {
readonly box!: Box | undefined; readonly box!: Box | undefined;
} }
export class UpdateGraphBreadcrumbPath extends JsMessage {
readonly breadcrumbPath!: bigint[];
}
export class UpdateClickTargets extends JsMessage { export class UpdateClickTargets extends JsMessage {
readonly clickTargets!: FrontendClickTargets | undefined; readonly clickTargets!: FrontendClickTargets | undefined;
} }
@@ -57,6 +62,10 @@ export class UpdateContextMenuInformation extends JsMessage {
readonly contextMenuInformation!: ContextMenuInformation | undefined; readonly contextMenuInformation!: ContextMenuInformation | undefined;
} }
export class UpdateContextDuringEvaluation extends JsMessage {
readonly contextDuringEvaluation!: [bigint, number, string][];
}
export class UpdateImportsExports extends JsMessage { export class UpdateImportsExports extends JsMessage {
@ImportsToVec2Array @ImportsToVec2Array
readonly imports!: { outputMetadata: FrontendGraphOutput; position: XY }[]; readonly imports!: { outputMetadata: FrontendGraphOutput; position: XY }[];
@@ -109,7 +118,7 @@ export class UpdateNodeGraphWires extends JsMessage {
readonly wires!: WireUpdate[]; readonly wires!: WireUpdate[];
} }
export class ClearAllNodeGraphWires extends JsMessage {} export class ClearAllNodeGraphWirePaths extends JsMessage {}
export class UpdateNodeGraphTransform extends JsMessage { export class UpdateNodeGraphTransform extends JsMessage {
readonly transform!: NodeGraphTransform; readonly transform!: NodeGraphTransform;
@@ -128,6 +137,10 @@ export class UpdateThumbnails extends JsMessage {
readonly add!: [bigint, string][]; readonly add!: [bigint, string][];
readonly clear!: bigint[]; readonly clear!: bigint[];
readonly wireSNIUpdates!: WireSNIUpdate[];
readonly layerSNIUpdates!: FrontendLayerSNIUpdate[];
} }
export class UpdateNodeGraphSelection extends JsMessage { export class UpdateNodeGraphSelection extends JsMessage {
@@ -266,6 +279,8 @@ export class FrontendGraphOutput {
export class FrontendNode { export class FrontendNode {
readonly isLayer!: boolean; readonly isLayer!: boolean;
layerThumbnailSNI!: bigint | undefined;
readonly canBeLayer!: boolean; readonly canBeLayer!: boolean;
readonly id!: bigint; readonly id!: bigint;
@@ -302,6 +317,12 @@ export class FrontendNode {
readonly uiOnly!: boolean; readonly uiOnly!: boolean;
} }
export class FrontendLayerSNIUpdate {
readonly id!: bigint;
readonly sni!: bigint | undefined;
}
export class FrontendNodeType { export class FrontendNodeType {
readonly name!: string; readonly name!: string;
@@ -317,13 +338,12 @@ export class NodeGraphTransform {
} }
export class WirePath { export class WirePath {
readonly pathString!: string; pathString!: string;
readonly dataType!: FrontendGraphDataType; readonly dataType!: FrontendGraphDataType;
readonly thick!: boolean; readonly thick!: boolean;
readonly dashed!: boolean; readonly dashed!: boolean;
@TupleToVec2 readonly center!: [number, number] | undefined;
readonly center!: XY | undefined; sni!: bigint | undefined;
readonly inputSni!: bigint;
} }
export class WireUpdate { export class WireUpdate {
@@ -332,6 +352,12 @@ export class WireUpdate {
readonly wirePathUpdate!: WirePath | undefined; readonly wirePathUpdate!: WirePath | undefined;
} }
export class WireSNIUpdate {
readonly id!: bigint;
readonly inputIndex!: number;
readonly sni!: bigint | undefined;
}
export class IndexedDbDocumentDetails extends DocumentDetails { export class IndexedDbDocumentDetails extends DocumentDetails {
@Transform(({ value }: { value: bigint }) => value.toString()) @Transform(({ value }: { value: bigint }) => value.toString())
id!: string; id!: string;
@@ -1624,7 +1650,7 @@ type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHand
type MessageMaker = typeof JsMessage | JSMessageFactory; type MessageMaker = typeof JsMessage | JSMessageFactory;
export const messageMakers: Record<string, MessageMaker> = { export const messageMakers: Record<string, MessageMaker> = {
ClearAllNodeGraphWires, ClearAllNodeGraphWirePaths,
DisplayDialog, DisplayDialog,
DisplayDialogDismiss, DisplayDialogDismiss,
DisplayDialogPanic, DisplayDialogPanic,
@@ -1653,8 +1679,10 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerVisitLink, TriggerVisitLink,
UpdateActiveDocument, UpdateActiveDocument,
UpdateBox, UpdateBox,
UpdateGraphBreadcrumbPath,
UpdateClickTargets, UpdateClickTargets,
UpdateContextMenuInformation, UpdateContextMenuInformation,
UpdateContextDuringEvaluation,
UpdateDialogButtons, UpdateDialogButtons,
UpdateDialogColumn1, UpdateDialogColumn1,
UpdateDialogColumn2, UpdateDialogColumn2,
+55 -8
View File
@@ -9,11 +9,13 @@ import {
type FrontendNode, type FrontendNode,
type FrontendNodeType, type FrontendNodeType,
type WirePath, type WirePath,
ClearAllNodeGraphWires, ClearAllNodeGraphWirePaths,
SendUIMetadata, SendUIMetadata,
UpdateBox, UpdateBox,
UpdateGraphBreadcrumbPath,
UpdateClickTargets, UpdateClickTargets,
UpdateContextMenuInformation, UpdateContextMenuInformation,
UpdateContextDuringEvaluation,
UpdateInSelectedNetwork, UpdateInSelectedNetwork,
UpdateImportReorderIndex, UpdateImportReorderIndex,
UpdateExportReorderIndex, UpdateExportReorderIndex,
@@ -32,6 +34,7 @@ import {
export function createNodeGraphState(editor: Editor) { export function createNodeGraphState(editor: Editor) {
const { subscribe, update } = writable({ const { subscribe, update } = writable({
box: undefined as Box | undefined, box: undefined as Box | undefined,
breadcrumbPath: [] as bigint[],
clickTargets: undefined as FrontendClickTargets | undefined, clickTargets: undefined as FrontendClickTargets | undefined,
contextMenuInformation: undefined as ContextMenuInformation | undefined, contextMenuInformation: undefined as ContextMenuInformation | undefined,
layerWidths: new Map<bigint, number>(), layerWidths: new Map<bigint, number>(),
@@ -43,8 +46,9 @@ export function createNodeGraphState(editor: Editor) {
addExport: undefined as { x: number; y: number } | undefined, addExport: undefined as { x: number; y: number } | undefined,
nodes: new Map<bigint, FrontendNode>(), nodes: new Map<bigint, FrontendNode>(),
visibleNodes: new Set<bigint>(), visibleNodes: new Set<bigint>(),
/// The index is the exposed input index. The exports have a first key value of u32::MAX. /// The first key is the document node id. The index is the actual input index. The exports have a first key value of u32::MAX.
wires: new Map<bigint, Map<number, WirePath>>(), wires: new Map<bigint, Map<number, WirePath>>(),
/// The first key is the caller stable node id
wirePathInProgress: undefined as WirePath | undefined, wirePathInProgress: undefined as WirePath | undefined,
nodeDescriptions: new Map<string, string>(), nodeDescriptions: new Map<string, string>(),
nodeTypes: [] as FrontendNodeType[], nodeTypes: [] as FrontendNodeType[],
@@ -70,6 +74,13 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateGraphBreadcrumbPath, (updateGraphBreadcrumbPath) => {
update((state) => {
state.breadcrumbPath = updateGraphBreadcrumbPath.breadcrumbPath;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateClickTargets, (UpdateClickTargets) => { editor.subscriptions.subscribeJsMessage(UpdateClickTargets, (UpdateClickTargets) => {
update((state) => { update((state) => {
state.clickTargets = UpdateClickTargets.clickTargets; state.clickTargets = UpdateClickTargets.clickTargets;
@@ -82,6 +93,11 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
// editor.subscriptions.subscribeJsMessage(UpdateContextDuringEvaluation, (updateContextDuringEvaluation) => {
// update((state) => {
// return state;
// });
// });
editor.subscriptions.subscribeJsMessage(UpdateImportReorderIndex, (updateImportReorderIndex) => { editor.subscriptions.subscribeJsMessage(UpdateImportReorderIndex, (updateImportReorderIndex) => {
update((state) => { update((state) => {
state.reorderImportIndex = updateImportReorderIndex.importIndex; state.reorderImportIndex = updateImportReorderIndex.importIndex;
@@ -118,7 +134,6 @@ export function createNodeGraphState(editor: Editor) {
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => { editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => {
// console.log(updateNodeGraphNodes);
update((state) => { update((state) => {
state.nodes.clear(); state.nodes.clear();
updateNodeGraphNodes.nodes.forEach((node) => { updateNodeGraphNodes.nodes.forEach((node) => {
@@ -127,6 +142,7 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => { editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => {
update((state) => { update((state) => {
state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes); state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes);
@@ -143,17 +159,33 @@ export function createNodeGraphState(editor: Editor) {
state.wires.set(wireUpdate.id, inputMap); state.wires.set(wireUpdate.id, inputMap);
} }
if (wireUpdate.wirePathUpdate !== undefined) { if (wireUpdate.wirePathUpdate !== undefined) {
inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate); const existing = inputMap.get(wireUpdate.inputIndex);
if (existing) {
inputMap.set(wireUpdate.inputIndex, {
...wireUpdate.wirePathUpdate,
sni: existing.sni,
});
} else {
inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate);
}
} else { } else {
inputMap.delete(wireUpdate.inputIndex); const existing = inputMap.get(wireUpdate.inputIndex);
if (existing) {
existing.pathString = "";
}
} }
}); });
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWires, (_) => { editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWirePaths, (_) => {
update((state) => { update((state) => {
state.wires.clear(); for (const [, innerMap] of state.wires) {
for (const [, wirePath] of innerMap) {
wirePath.pathString = "";
}
}
return state; return state;
}); });
}); });
@@ -178,7 +210,22 @@ export function createNodeGraphState(editor: Editor) {
for (const id of updateThumbnails.clear) { for (const id of updateThumbnails.clear) {
state.thumbnails.set(id, ""); state.thumbnails.set(id, "");
} }
// console.log("thumbnails: ", state.thumbnails); updateThumbnails.wireSNIUpdates.forEach((wireUpdate) => {
const inputMap = state.wires.get(wireUpdate.id);
if (inputMap) {
const wire = inputMap.get(wireUpdate.inputIndex);
if (wire) {
wire.sni = wireUpdate.sni;
}
}
});
updateThumbnails.layerSNIUpdates.forEach((wireUpdate) => {
const node = state.nodes.get(wireUpdate.id);
if (node) {
node.layerThumbnailSNI = wireUpdate.sni;
}
});
return state; return state;
}); });
}); });
+138 -26
View File
@@ -1,7 +1,9 @@
use dyn_any::StaticType;
use glam::{DAffine2, UVec2}; use glam::{DAffine2, UVec2};
use crate::transform::Footprint; use crate::transform::Footprint;
use std::any::Any; use std::any::Any;
use std::fmt;
use std::panic::Location; use std::panic::Location;
use std::sync::Arc; use std::sync::Arc;
@@ -26,6 +28,14 @@ pub trait ExtractRealTime {
fn try_real_time(&self) -> Option<f64>; fn try_real_time(&self) -> Option<f64>;
} }
pub trait ModifyDownstreamTransform: ExtractAll + CloneVarArgs {
fn apply_modification(self, modification: &DAffine2) -> Context;
}
pub trait WithIndex: ExtractAll + CloneVarArgs {
fn with_index(&self, index: usize) -> Context;
}
pub trait ExtractAnimationTime { pub trait ExtractAnimationTime {
fn try_animation_time(&self) -> Option<f64>; fn try_animation_time(&self) -> Option<f64>;
} }
@@ -50,7 +60,7 @@ pub trait ExtractAll: ExtractFootprint + ExtractDownstreamTransform + ExtractInd
impl<T: ?Sized + ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {} impl<T: ?Sized + ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
#[derive(Debug, Clone, PartialEq)] #[derive(Clone, PartialEq)]
#[repr(u8)] #[repr(u8)]
pub enum ContextDependency { pub enum ContextDependency {
ExtractFootprint = 0b10000000, ExtractFootprint = 0b10000000,
@@ -62,7 +72,7 @@ pub enum ContextDependency {
ExtractVarArgs = 0b00000100, ExtractVarArgs = 0b00000100,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct ContextDependencies(pub u8); pub struct ContextDependencies(pub u8);
impl ContextDependencies { impl ContextDependencies {
@@ -99,6 +109,34 @@ impl ContextDependencies {
} }
} }
impl fmt::Debug for ContextDependencies {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut set = Vec::new();
let bits = self.0;
if bits & ContextDependency::ExtractFootprint as u8 != 0 {
set.push("ExtractFootprint");
}
if bits & ContextDependency::ExtractDownstreamTransform as u8 != 0 {
set.push("ExtractDownstreamTransform");
}
if bits & ContextDependency::ExtractRealTime as u8 != 0 {
set.push("ExtractRealTime");
}
if bits & ContextDependency::ExtractAnimationTime as u8 != 0 {
set.push("ExtractAnimationTime");
}
if bits & ContextDependency::ExtractIndex as u8 != 0 {
set.push("ExtractIndex");
}
if bits & ContextDependency::ExtractVarArgs as u8 != 0 {
set.push("ExtractVarArgs");
}
f.debug_list().entries(set).finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum VarArgsResult { pub enum VarArgsResult {
IndexOutOfBounds, IndexOutOfBounds,
@@ -110,7 +148,7 @@ impl Ctx for () {}
impl Ctx for Footprint {} impl Ctx for Footprint {}
impl ExtractFootprint for () { impl ExtractFootprint for () {
fn try_footprint(&self) -> Option<&Footprint> { fn try_footprint(&self) -> Option<&Footprint> {
log::error!("tried to extract footprint form (), {}", Location::caller()); log::error!("tried to extract footprint from (), {}", Location::caller());
None None
} }
} }
@@ -129,7 +167,7 @@ impl<T: ExtractFootprint + Sync> ExtractFootprint for Option<T> {
impl ExtractDownstreamTransform for () { impl ExtractDownstreamTransform for () {
fn try_downstream_transform(&self) -> Option<&DAffine2> { fn try_downstream_transform(&self) -> Option<&DAffine2> {
log::error!("tried to extract downstream transform form (), {}", Location::caller()); log::error!("tried to extract downstream transform from (), {}", Location::caller());
None None
} }
} }
@@ -146,6 +184,42 @@ impl<T: ExtractDownstreamTransform + Sync> ExtractDownstreamTransform for Option
} }
} }
impl<T: ExtractDownstreamTransform + Sync> ExtractDownstreamTransform for Arc<T> {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
(**self).try_downstream_transform()
}
}
impl ExtractDownstreamTransform for OwnedContextImpl {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
self.downstream_transform.as_ref()
}
}
impl<T: ExtractAll + CloneVarArgs + Sync> ModifyDownstreamTransform for Option<T> {
fn apply_modification(self, modification: &DAffine2) -> Context {
if let Some(inner) = self {
let mut context = OwnedContextImpl::from(inner);
context.try_apply_downstream_transform(modification);
context.into_context()
} else {
None
}
}
}
impl<T: Ctx + ExtractAll + CloneVarArgs + Sync> WithIndex for Option<T> {
fn with_index(&self, index: usize) -> Context {
if let Some(inner) = self {
let mut context = OwnedContextImpl::from(inner.clone());
context.set_index(index);
context.into_context()
} else {
None
}
}
}
impl<T: ExtractRealTime + Sync> ExtractRealTime for Option<T> { impl<T: ExtractRealTime + Sync> ExtractRealTime for Option<T> {
fn try_real_time(&self) -> Option<f64> { fn try_real_time(&self) -> Option<f64> {
self.as_ref().and_then(|x| x.try_real_time()) self.as_ref().and_then(|x| x.try_real_time())
@@ -178,12 +252,6 @@ impl<T: ExtractFootprint + Sync> ExtractFootprint for Arc<T> {
} }
} }
impl<T: ExtractDownstreamTransform + Sync> ExtractDownstreamTransform for Arc<T> {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
(**self).try_downstream_transform()
}
}
impl<T: ExtractRealTime + Sync> ExtractRealTime for Arc<T> { impl<T: ExtractRealTime + Sync> ExtractRealTime for Arc<T> {
fn try_real_time(&self) -> Option<f64> { fn try_real_time(&self) -> Option<f64> {
(**self).try_real_time() (**self).try_real_time()
@@ -237,12 +305,6 @@ impl ExtractFootprint for OwnedContextImpl {
} }
} }
impl ExtractDownstreamTransform for OwnedContextImpl {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
self.downstream_transform.as_ref()
}
}
impl ExtractRealTime for OwnedContextImpl { impl ExtractRealTime for OwnedContextImpl {
fn try_real_time(&self) -> Option<f64> { fn try_real_time(&self) -> Option<f64> {
self.real_time self.real_time
@@ -395,6 +457,16 @@ impl OwnedContextImpl {
self.parent = None self.parent = None
} }
} }
pub fn to_editor_context(&self) -> EditorContext {
EditorContext {
footprint: self.footprint,
downstream_transform: self.downstream_transform,
real_time: self.real_time,
animation_time: self.animation_time,
index: self.index,
}
}
} }
impl OwnedContextImpl { impl OwnedContextImpl {
@@ -404,9 +476,9 @@ impl OwnedContextImpl {
pub fn set_downstream_transform(&mut self, transform: DAffine2) { pub fn set_downstream_transform(&mut self, transform: DAffine2) {
self.downstream_transform = Some(transform); self.downstream_transform = Some(transform);
} }
pub fn try_apply_downstream_transform(&mut self, transform: DAffine2) { pub fn try_apply_downstream_transform(&mut self, transform: &DAffine2) {
if let Some(downstream_transform) = self.downstream_transform { if let Some(downstream_transform) = self.downstream_transform {
self.downstream_transform = Some(downstream_transform * transform); self.downstream_transform = Some(downstream_transform * *transform);
} }
} }
pub fn set_real_time(&mut self, time: f64) { pub fn set_real_time(&mut self, time: f64) {
@@ -434,14 +506,10 @@ impl OwnedContextImpl {
self.animation_time = Some(animation_time); self.animation_time = Some(animation_time);
self self
} }
pub fn with_index(mut self, index: usize) -> Self { // pub fn with_index(mut self, index: usize) -> Self {
if let Some(current_index) = &mut self.index { // self.index = Some(index);
current_index.push(index); // self
} else { // }
self.index = Some(vec![index]);
}
self
}
pub fn into_context(self) -> Option<Arc<Self>> { pub fn into_context(self) -> Option<Arc<Self>> {
Some(Arc::new(self)) Some(Arc::new(self))
} }
@@ -465,6 +533,50 @@ impl OwnedContextImpl {
} }
} }
#[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>,
// #[serde(skip)]
// pub editor_var_args: Option<(Vec<String>, Vec<Arc<Box<[dyn std::any::Any + 'static + std::panic::UnwindSafe]>>>)>,
}
unsafe impl StaticType for EditorContext {
type Static = EditorContext;
}
impl EditorContext {
pub fn to_owned_context(&self) -> OwnedContextImpl {
let mut context = OwnedContextImpl::default();
if let Some(footprint) = self.footprint {
context.set_footprint(footprint);
}
if let Some(footprint) = self.footprint {
context.set_footprint(footprint);
}
// if let Some(downstream_transform) = self.downstream_transform {
// context.set_downstream_transform(downstream_transform);
// }
if let Some(real_time) = self.real_time {
context.set_real_time(real_time);
}
if let Some(animation_time) = self.animation_time {
context.set_animation_time(animation_time);
}
if let Some(index) = self.index {
context.set_index(index);
}
context
// if let Some(editor_var_args) = self.editor_var_args {
// let (variable_names, values)
// context.set_varargs((variable_names, values))
// }
}
}
// #[derive(Default, Clone, Copy, dyn_any::DynAny)] // #[derive(Default, Clone, Copy, dyn_any::DynAny)]
// pub struct ContextImpl<'a> { // pub struct ContextImpl<'a> {
// pub(crate) footprint: Option<&'a Footprint>, // pub(crate) footprint: Option<&'a Footprint>,
+4 -10
View File
@@ -4,10 +4,9 @@ use crate::instances::{Instance, Instances};
use crate::math::quad::Quad; use crate::math::quad::Quad;
use crate::raster::image::Image; use crate::raster::image::Image;
use crate::raster_types::{CPU, GPU, Raster, RasterDataTable}; use crate::raster_types::{CPU, GPU, Raster, RasterDataTable};
use crate::transform::TransformMut;
use crate::uuid::NodeId; use crate::uuid::NodeId;
use crate::vector::{VectorData, VectorDataTable}; use crate::vector::{VectorData, VectorDataTable};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use crate::{ Color, Context, Ctx, ModifyDownstreamTransform};
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
use std::hash::Hash; use std::hash::Hash;
@@ -457,7 +456,7 @@ async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTabl
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>( async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx, ctx: impl Ctx + ModifyDownstreamTransform,
#[implementations( #[implementations(
Context -> GraphicGroupTable, Context -> GraphicGroupTable,
Context -> VectorDataTable, Context -> VectorDataTable,
@@ -471,13 +470,8 @@ async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
background: Color, background: Color,
clip: bool, clip: bool,
) -> Artboard { ) -> Artboard {
let footprint = ctx.try_footprint().copied(); let modified_ctx = ctx.apply_modification(&DAffine2::from_translation(location.as_dvec2()));
let mut new_ctx = OwnedContextImpl::from(ctx); let graphic_group = contents.eval(modified_ctx).await;
if let Some(mut footprint) = footprint {
footprint.translate(location.as_dvec2());
new_ctx = new_ctx.with_footprint(footprint);
}
let graphic_group = contents.eval(new_ctx.into_context()).await;
Artboard { Artboard {
graphic_group: graphic_group.into(), graphic_group: graphic_group.into(),
+7 -2
View File
@@ -32,6 +32,7 @@ pub mod value;
pub mod vector; pub mod vector;
pub use crate as graphene_core; pub use crate as graphene_core;
use crate::memo::MonitorIntrospectResult;
pub use blending::*; pub use blending::*;
pub use context::*; pub use context::*;
pub use ctor; pub use ctor;
@@ -61,10 +62,14 @@ pub trait Node<'i, Input> {
} }
// If check if evaluated is true, then it returns None if the node has not been evaluated since the last introspection // If check if evaluated is true, then it returns None if the node has not been evaluated since the last introspection
fn introspect(&self, _check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> { fn introspect(&self) -> MonitorIntrospectResult {
log::warn!("Node::introspect not implemented for {}", std::any::type_name::<Self>()); log::warn!("Node::introspect not implemented for {}", std::any::type_name::<Self>());
None MonitorIntrospectResult::Error
} }
fn permanently_enable_cache(&self) {}
fn cache_first_evaluation(&self) {}
} }
mod types; mod types;
+117 -31
View File
@@ -7,14 +7,37 @@ use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
#[derive(Debug)]
pub enum MonitorMemoNodeState {
Disabled,
// Stores the first execution, then gets set first execution result, which stores if the value changed
StoreFirstEvaluation,
// Gets set back to disabled on introspection, and stores a boolean for if the value changed since the last introspection
FirstEvaluationResult(bool),
// Acts as a normal cache node, and stores a boolean for if the value changed since the last introspection
Enabled(bool),
}
#[derive(Clone, Debug)]
pub enum MonitorIntrospectResult {
// If trying to inspect a none that cannot be introspected
Error,
Disabled,
// The cache node has not been evaluated since the state was set to StoreFirstEvaluation/Enabled
NotEvaluated,
// If the monitor node was evaluated, then its data must exist, so it is not an option.
// The boolean represents if the data changed since the last introspection
Evaluated((std::sync::Arc<dyn std::any::Any + Send + Sync>, bool)),
}
/// Caches the output of a given Node and acts as a proxy /// Caches the output of a given Node and acts as a proxy
#[derive(Default)]
pub struct MonitorMemoNode<T, CachedNode> { pub struct MonitorMemoNode<T, CachedNode> {
// Introspection cache, uses the hash of the nullified context with default var args // Introspection cache, uses the hash of the nullified context with default var args
// cache: Arc<Mutex<std::collections::HashMap<u64, Arc<T>>>>, // cache: Arc<Mutex<std::collections::HashMap<u64, Arc<T>>>>,
cache: Arc<Mutex<Option<(u64, Arc<T>)>>>, cache: Arc<Mutex<Option<(u64, Arc<T>)>>>,
node: CachedNode, node: CachedNode,
changed_since_last_eval: Arc<Mutex<bool>>, hash_on_last_introspection: Arc<Mutex<u64>>,
state: Arc<Mutex<MonitorMemoNodeState>>,
} }
impl<'i, I: Hash + 'i + std::fmt::Debug, T: 'static + Clone + Send + Sync, CachedNode: 'i> Node<'i, I> for MonitorMemoNode<T, CachedNode> impl<'i, I: Hash + 'i + std::fmt::Debug, T: 'static + Clone + Send + Sync, CachedNode: 'i> Node<'i, I> for MonitorMemoNode<T, CachedNode>
where where
@@ -26,47 +49,110 @@ where
type Output = DynFuture<'i, T>; type Output = DynFuture<'i, T>;
fn eval(&'i self, input: I) -> Self::Output { fn eval(&'i self, input: I) -> Self::Output {
let mut hasher = DefaultHasher::new(); let mut state = self.state.lock().unwrap();
input.hash(&mut hasher);
let hash = hasher.finish();
if let Some(data) = self.cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) { // log::debug!("Monitor memo node state: {:?}", *state);
let cloned_data = (*data).clone();
Box::pin(async move { cloned_data }) if matches!(*state, MonitorMemoNodeState::Disabled | MonitorMemoNodeState::FirstEvaluationResult(_)) {
} else { return Box::pin(self.node.eval(input));
let fut = self.node.eval(input); }
let cache = self.cache.clone();
*self.changed_since_last_eval.lock().unwrap() = true; let hash = {
Box::pin(async move { let mut hasher = DefaultHasher::new();
let value = fut.await; input.hash(&mut hasher);
*cache.lock().unwrap() = Some((hash, Arc::new(value.clone()))); hasher.finish()
value };
})
// log::debug!(
// "Monitor memo node input: {:?}, hash: {:?}, previous_hash: {:?}",
// input,
// hash,
// self.cache.lock().unwrap().as_ref().map(|(h, _)| *h)
// );
let last_hash = *self.hash_on_last_introspection.lock().unwrap();
match &mut *state {
MonitorMemoNodeState::Enabled(changed) => {
*changed = last_hash != hash;
}
MonitorMemoNodeState::StoreFirstEvaluation => {
*state = MonitorMemoNodeState::FirstEvaluationResult(last_hash != hash);
}
_ => {}
}
let cache_guard = self.cache.lock().unwrap();
if let Some((cached_hash, cached_data)) = cache_guard.as_ref() {
if *cached_hash == hash {
let cloned_data = (**cached_data).clone();
return Box::pin(async move { cloned_data });
}
}
drop(cache_guard);
Box::pin(async move {
let value = self.node.eval(input).await;
*self.cache.lock().unwrap() = Some((hash, Arc::new(value.clone())));
value
})
}
fn introspect(&self) -> MonitorIntrospectResult {
let mut state_guard = self.state.lock().unwrap();
match *state_guard {
MonitorMemoNodeState::Disabled => {
// Make sure to set the state to "StoreFirstEvaluation" or "Enabled" before trying to introspect
log::error!("Cannot introspect disabled monitor memo node");
MonitorIntrospectResult::Disabled
}
MonitorMemoNodeState::StoreFirstEvaluation => MonitorIntrospectResult::NotEvaluated,
MonitorMemoNodeState::FirstEvaluationResult(changed_since_last_introspection) => {
let (hash, cache_value) = self
.cache
.lock()
.unwrap()
.as_ref()
.map(|(hash, data)| (*hash, (*data).clone() as Arc<dyn std::any::Any + Send + Sync>))
.expect("Evaluated cache node must store data");
*self.hash_on_last_introspection.lock().unwrap() = hash;
*state_guard = MonitorMemoNodeState::Disabled;
MonitorIntrospectResult::Evaluated((cache_value, changed_since_last_introspection))
}
MonitorMemoNodeState::Enabled(changed_since_last_introspection) => {
let cache = self.cache.lock().unwrap().as_ref().map(|(hash, data)| (*hash, (*data).clone() as Arc<dyn std::any::Any + Send + Sync>));
match cache {
Some((hash, cache_value)) => {
*self.hash_on_last_introspection.lock().unwrap() = hash;
MonitorIntrospectResult::Evaluated((cache_value, changed_since_last_introspection))
}
None => MonitorIntrospectResult::NotEvaluated,
}
}
} }
} }
// TODO: Consider returning a reference to the entire cache so the frontend reference is automatically updated as the context changes fn permanently_enable_cache(&self) {
fn introspect(&self, check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> { *self.state.lock().unwrap() = MonitorMemoNodeState::Enabled(false);
let mut changed = self.changed_since_last_eval.lock().unwrap(); }
if check_if_evaluated {
if !*changed {
return None;
}
}
*changed = false;
let cache_guard = self.cache.lock().unwrap(); fn cache_first_evaluation(&self) {
let cached = cache_guard.as_ref().expect("Cached data should always be evaluated before introspection"); if matches!(*self.state.lock().unwrap(), MonitorMemoNodeState::Enabled(_)) {
Some(cached.1.clone() as Arc<dyn std::any::Any + Send + Sync>) return;
}
*self.state.lock().unwrap() = MonitorMemoNodeState::StoreFirstEvaluation;
} }
} }
impl<T, CachedNode> MonitorMemoNode<T, CachedNode> { impl<T, CachedNode> MonitorMemoNode<T, CachedNode> {
pub fn new(node: CachedNode) -> MonitorMemoNode<T, CachedNode> { pub fn new(node: CachedNode, state: MonitorMemoNodeState) -> MonitorMemoNode<T, CachedNode> {
MonitorMemoNode { MonitorMemoNode {
cache: Default::default(), cache: Default::default(),
node, node,
changed_since_last_eval: Arc::new(Mutex::new(true)), hash_on_last_introspection: Arc::new(Mutex::new(0)),
state: Arc::new(Mutex::new(state)),
} }
} }
} }
+12 -3
View File
@@ -1,3 +1,4 @@
use crate::memo::MonitorMemoNodeState;
use crate::{ContextDependencies, Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend}; use crate::{ContextDependencies, Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use std::borrow::Cow; use std::borrow::Cow;
@@ -134,7 +135,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 CacheConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>; pub type CacheConstructor = fn(SharedNodeContainer, MonitorMemoNodeState) -> TypeErasedBox<'static>;
#[derive(Clone)] #[derive(Clone)]
pub struct NodeContainer { pub struct NodeContainer {
@@ -290,8 +291,16 @@ where
} }
} }
fn introspect(&self, check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> { fn introspect(&self) -> crate::memo::MonitorIntrospectResult {
self.node.introspect(check_if_evaluated) self.node.introspect()
}
fn permanently_enable_cache(&self) {
self.node.permanently_enable_cache();
}
fn cache_first_evaluation(&self) {
self.node.cache_first_evaluation();
} }
fn reset(&self) { fn reset(&self) {
+4 -2
View File
@@ -110,8 +110,10 @@ impl Footprint {
quality: RenderQuality::Full, quality: RenderQuality::Full,
}; };
pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox { pub fn viewport_bounds_in_local_space(&self, downstream_transform: &DAffine2) -> AxisAlignedBbox {
let inverse = self.transform.inverse(); // TODO: Check if this is the correct way to apply downstream transforms
let transform = self.transform * *downstream_transform;
let inverse = transform.inverse();
let start = inverse.transform_point2((0., 0.).into()); let start = inverse.transform_point2((0., 0.).into());
let end = inverse.transform_point2(self.resolution.as_dvec2()); let end = inverse.transform_point2(self.resolution.as_dvec2());
AxisAlignedBbox { start, end } AxisAlignedBbox { start, end }
+5 -47
View File
@@ -1,14 +1,14 @@
use crate::instances::Instances; use crate::instances::Instances;
use crate::raster_types::{CPU, GPU, RasterDataTable}; use crate::raster_types::{CPU, GPU, RasterDataTable};
use crate::transform::{ApplyTransform, Footprint, Transform}; use crate::transform::{Transform};
use crate::vector::VectorDataTable; use crate::vector::VectorDataTable;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl}; use crate::{ Context, Ctx, GraphicGroupTable, ModifyDownstreamTransform, };
use core::f64; use core::f64;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn transform<T: 'n + 'static>( async fn transform<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll, ctx: impl Ctx + ModifyDownstreamTransform,
#[implementations( #[implementations(
Context -> VectorDataTable, Context -> VectorDataTable,
Context -> GraphicGroupTable, Context -> GraphicGroupTable,
@@ -23,15 +23,9 @@ async fn transform<T: 'n + 'static>(
) -> Instances<T> { ) -> Instances<T> {
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]); let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
let footprint = ctx.try_footprint().copied(); let modified_ctx = ctx.apply_modification(&matrix);
let mut ctx = OwnedContextImpl::from(ctx); let mut transform_target = transform_target.eval(modified_ctx).await;
if let Some(mut footprint) = footprint {
footprint.apply_transform(&matrix);
ctx = ctx.with_footprint(footprint);
}
let mut transform_target = transform_target.eval(ctx.into_context()).await;
for data_transform in transform_target.instance_mut_iter() { for data_transform in transform_target.instance_mut_iter() {
*data_transform.transform = matrix * *data_transform.transform; *data_transform.transform = matrix * *data_transform.transform;
@@ -51,39 +45,3 @@ fn replace_transform<Data, TransformInput: Transform>(
} }
data data
} }
#[node_macro::node(category("Debug"))]
async fn boundless_footprint<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> String,
Context -> f64,
)]
transform_target: impl Node<Context<'static>, Output = T>,
) -> T {
let ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::BOUNDLESS);
transform_target.eval(ctx.into_context()).await
}
#[node_macro::node(category("Debug"))]
async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> String,
Context -> f64,
)]
transform_target: impl Node<Context<'static>, Output = T>,
) -> T {
let ctx = OwnedContextImpl::from(ctx).with_real_time(0.);
transform_target.eval(ctx.into_context()).await
}
@@ -1,12 +1,12 @@
use crate::instances::{InstanceRef, Instances}; use crate::instances::{InstanceRef, Instances};
use crate::raster_types::{CPU, RasterDataTable}; use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable; use crate::vector::VectorDataTable;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl}; use crate::{ Context, Ctx, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, WithIndex};
use glam::DVec2; use glam::DVec2;
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))] #[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + 'static>( async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx, ctx: impl Ctx + WithIndex,
points: VectorDataTable, points: VectorDataTable,
#[implementations( #[implementations(
Context -> GraphicGroupTable, Context -> GraphicGroupTable,
@@ -22,8 +22,7 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + '
let mut iteration = async |index, point| { let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point); let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(("Transformed point", Box::new(transformed_point))); let generated_instance = instance.eval(ctx.with_index(index)).await;
let generated_instance = instance.eval(new_ctx.into_context()).await;
for mut instanced in generated_instance.instance_iter() { for mut instanced in generated_instance.instance_iter() {
instanced.transform.translation = transformed_point; instanced.transform.translation = transformed_point;
@@ -48,7 +47,7 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + '
#[node_macro::node(category("Instancing"), path(graphene_core::vector))] #[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'static>( async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx, ctx: impl Ctx + WithIndex,
#[implementations( #[implementations(
Context -> GraphicGroupTable, Context -> GraphicGroupTable,
Context -> VectorDataTable, Context -> VectorDataTable,
@@ -65,8 +64,7 @@ async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'sta
for index in 0..count { for index in 0..count {
let index = if reverse { count - index - 1 } else { index }; let index = if reverse { count - index - 1 } else { index };
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index); let generated_instance = instance.eval(ctx.with_index(index)).await;
let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in generated_instance.instance_iter() { for instanced in generated_instance.instance_iter() {
result_table.push(instanced); result_table.push(instanced);
+6 -17
View File
@@ -8,19 +8,14 @@ use crate::bounds::BoundingBox;
use crate::instances::{Instance, InstanceMut, Instances}; use crate::instances::{Instance, InstanceMut, Instances};
use crate::raster_types::{CPU, GPU, RasterDataTable}; use crate::raster_types::{CPU, GPU, RasterDataTable};
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue}; use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
use crate::transform::{Footprint, ReferencePoint, Transform}; use crate::transform::{ReferencePoint, Transform};
use crate::vector::PointDomain;
use crate::vector::algorithms::bezpath_algorithms::{eval_pathseg_euclidean, is_linear};
use crate::vector::algorithms::merge_by_distance::MergeByDistanceExt; use crate::vector::algorithms::merge_by_distance::MergeByDistanceExt;
use crate::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType}; use crate::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType};
use crate::vector::misc::{handles_to_segment, segment_to_handles}; use crate::vector::misc::{handles_to_segment, segment_to_handles};
use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin}; use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use crate::vector::{FillId, RegionId}; use crate::vector::{FillId, PointDomain, RegionId};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl}; use crate::{Color, Ctx, GraphicElement, GraphicGroupTable};
use bezier_rs::{Join, ManipulatorGroup, Subpath};
use bezier_rs::{BezierHandles, Join, ManipulatorGroup, Subpath};
use core::f64::consts::PI;
use core::hash::{Hash, Hasher};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Shape}; use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Shape};
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
@@ -2063,10 +2058,7 @@ async fn path_length(_: impl Ctx, source: VectorDataTable) -> f64 {
} }
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 { async fn area(_ctx: impl Ctx, vector_data: VectorDataTable) -> f64 {
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
let vector_data = vector_data.eval(new_ctx).await;
vector_data vector_data
.instance_ref_iter() .instance_ref_iter()
.map(|vector_data_instance| { .map(|vector_data_instance| {
@@ -2077,10 +2069,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
} }
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 { async fn centroid(_ctx: impl Ctx, vector_data: VectorDataTable, centroid_type: CentroidType) -> DVec2 {
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
let vector_data = vector_data.eval(new_ctx).await;
if vector_data.is_empty() { if vector_data.is_empty() {
return DVec2::ZERO; return DVec2::ZERO;
} }
@@ -6,7 +6,9 @@ fn compile_to_proto(c: &mut Criterion) {
for name in DEMO_ART { for name in DEMO_ART {
let network = load_from_name(name); let network = load_from_name(name);
c.bench_function(name, |b: &mut criterion::Bencher<'_>| b.iter_batched(|| network.clone(), |mut network| black_box(network.flatten()), criterion::BatchSize::SmallInput)); c.bench_function(name, |b: &mut criterion::Bencher<'_>| {
b.iter_batched(|| network.clone(), |mut network| black_box(network.compile()), criterion::BatchSize::SmallInput)
});
} }
} }
@@ -5,7 +5,7 @@ use iai_callgrind::{black_box, library_benchmark, library_benchmark_group, main}
#[library_benchmark] #[library_benchmark]
#[benches::with_setup(args = ["isometric-fountain", "painted-dreams", "procedural-string-lights", "parametric-dunescape", "red-dress", "valley-of-spires"], setup = load_from_name)] #[benches::with_setup(args = ["isometric-fountain", "painted-dreams", "procedural-string-lights", "parametric-dunescape", "red-dress", "valley-of-spires"], setup = load_from_name)]
pub fn compile_to_proto(mut input: NodeNetwork) { pub fn compile_to_proto(mut input: NodeNetwork) {
let _ = black_box(input.flatten()); let _ = black_box(input.compile());
} }
library_benchmark_group!(name = compile_group; benchmarks = compile_to_proto); library_benchmark_group!(name = compile_group; benchmarks = compile_to_proto);
+14 -11
View File
@@ -40,6 +40,9 @@ pub struct DocumentNode {
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step. /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.
#[serde(default = "return_true")] #[serde(default = "return_true")]
pub visible: bool, pub visible: bool,
// Represents whether the output of the node should be cached. This is set to true whenever a node feeds into another node with more context dependencies
#[serde(default)]
pub cache_output: bool,
pub manual_composition: Option<Type>, pub manual_composition: Option<Type>,
#[serde(default)] #[serde(default)]
pub skip_deduplication: bool, pub skip_deduplication: bool,
@@ -50,6 +53,7 @@ impl Hash for DocumentNode {
self.inputs.hash(state); self.inputs.hash(state);
self.implementation.hash(state); self.implementation.hash(state);
self.visible.hash(state); self.visible.hash(state);
self.cache_output.hash(state);
} }
} }
@@ -59,6 +63,7 @@ impl Default for DocumentNode {
inputs: Default::default(), inputs: Default::default(),
implementation: Default::default(), implementation: Default::default(),
visible: true, visible: true,
cache_output: false,
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: false, skip_deduplication: false,
} }
@@ -518,7 +523,7 @@ impl NodeNetwork {
/// Functions for compiling the network /// Functions for compiling the network
impl NodeNetwork { impl NodeNetwork {
// Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation // Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation
pub fn flatten(&mut self) -> Result<(ProtoNetwork, Vec<(OriginalLocation, SNI)>), String> { pub fn compile(&mut self) -> Result<(ProtoNetwork, Vec<(OriginalLocation, SNI)>), String> {
// These three arrays are stored in parallel // These three arrays are stored in parallel
let mut protonetwork = Vec::new(); let mut protonetwork = Vec::new();
@@ -544,8 +549,8 @@ impl NodeNetwork {
let Some(upstream_metadata) = upstream_metadata else { let Some(upstream_metadata) = upstream_metadata else {
panic!("All inputs should be when the upstream SNI was generated"); panic!("All inputs should be when the upstream SNI was generated");
}; };
if upstream_metadata.is_value { if !upstream_metadata.is_value {
context_dependencies.add_dependencies(&upstream_metadata.context_dependencies); context_dependencies.add_dependencies(&upstream_metadata.nullify);
} }
} }
// The context_dependencies are now the union of all inputs and the dependencies of the protonode. Set the dependencies of each input to the difference, which represents the data to nullify // The context_dependencies are now the union of all inputs and the dependencies of the protonode. Set the dependencies of each input to the difference, which represents the data to nullify
@@ -554,9 +559,9 @@ impl NodeNetwork {
panic!("All inputs should be when the upstream SNI was generated"); panic!("All inputs should be when the upstream SNI was generated");
}; };
match upstream_metadata.is_value { match upstream_metadata.is_value {
true => upstream_metadata.context_dependencies.difference(&context_dependencies), false => upstream_metadata.nullify.difference(&context_dependencies),
// If the upstream node is a Value node, do not nullify the context // If the upstream node is a Value node, do not nullify the context
false => upstream_metadata.context_dependencies = ContextDependencies::none(), true => upstream_metadata.nullify = ContextDependencies::none(),
} }
} }
(context_dependencies.clone(), false) (context_dependencies.clone(), false)
@@ -575,10 +580,7 @@ impl NodeNetwork {
}; };
(deduplicated_protonode.callers, deduplicated_protonode.original_location) (deduplicated_protonode.callers, deduplicated_protonode.original_location)
} else { } else {
( (std::mem::take(&mut protonode.callers), protonode.original_location.clone())
std::mem::take(&mut protonode.callers),
std::mem::replace(&mut protonode.original_location, OriginalLocation::Node(Vec::new())),
)
}; };
// Map the callers inputs to the generated stable node id // Map the callers inputs to the generated stable node id
@@ -592,7 +594,7 @@ impl NodeNetwork {
assert!(caller_index > current_protonode_index, "Caller index must be higher than current index"); assert!(caller_index > current_protonode_index, "Caller index must be higher than current index");
nodes.inputs[input_index] = Some(UpstreamInputMetadata { nodes.inputs[input_index] = Some(UpstreamInputMetadata {
input_sni: stable_node_id, input_sni: stable_node_id,
context_dependencies: protonode_context_dependencies.clone(), nullify: protonode_context_dependencies.clone(),
is_value: upstream_is_value, is_value: upstream_is_value,
}) })
} }
@@ -726,7 +728,7 @@ impl NodeNetwork {
log::error!("The node which was supposed to be flattened does not exist in the network, id {upstream_node_id}"); log::error!("The node which was supposed to be flattened does not exist in the network, id {upstream_node_id}");
return; return;
}; };
let cache_output = upstream_document_node.cache_output;
match &upstream_document_node.implementation { match &upstream_document_node.implementation {
DocumentNodeImplementation::Network(_node_network) => { DocumentNodeImplementation::Network(_node_network) => {
let traversal_input = AbsoluteInputConnector { let traversal_input = AbsoluteInputConnector {
@@ -766,6 +768,7 @@ impl NodeNetwork {
identifier, identifier,
inputs: vec![None; number_of_inputs], inputs: vec![None; number_of_inputs],
context_dependencies, context_dependencies,
cache_output,
}); });
let protonode = ProtoNode { let protonode = ProtoNode {
construction_args, construction_args,
+11 -18
View File
@@ -13,6 +13,7 @@ use graphene_core::uuid::NodeId;
use graphene_core::vector::style::Fill; use graphene_core::vector::style::Fill;
use graphene_core::{Color, MemoHash, Node, Type}; use graphene_core::{Color, MemoHash, Node, Type};
use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata}; use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata};
use std::cell::Cell;
use std::fmt::Display; use std::fmt::Display;
use std::hash::Hash; use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -396,6 +397,7 @@ impl Display for TaggedValue {
pub struct UpcastNode { pub struct UpcastNode {
value: MemoHash<TaggedValue>, value: MemoHash<TaggedValue>,
inspected: Cell<bool>,
} }
impl<'input> Node<'input, DAny<'input>> for UpcastNode { impl<'input> Node<'input, DAny<'input>> for UpcastNode {
type Output = FutureAny<'input>; type Output = FutureAny<'input>;
@@ -403,10 +405,15 @@ impl<'input> Node<'input, DAny<'input>> for UpcastNode {
fn eval(&'input self, _: DAny<'input>) -> Self::Output { fn eval(&'input self, _: DAny<'input>) -> Self::Output {
Box::pin(async move { self.value.clone().into_inner().to_dynany() }) Box::pin(async move { self.value.clone().into_inner().to_dynany() })
} }
fn introspect(&self) -> graphene_core::memo::MonitorIntrospectResult {
let inspected = self.inspected.replace(true);
graphene_core::memo::MonitorIntrospectResult::Evaluated((Arc::new(self.value.clone().into_inner()) as Arc<dyn std::any::Any + Send + Sync>, !inspected))
}
} }
impl UpcastNode { impl UpcastNode {
pub fn new(value: MemoHash<TaggedValue>) -> Self { pub fn new(value: MemoHash<TaggedValue>) -> Self {
Self { value } Self { value, inspected: Cell::new(false) }
} }
} }
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
@@ -426,8 +433,6 @@ impl<T: AsRef<U> + Sync + Send, U: Sync + Send> UpcastAsRefNode<T, U> {
} }
} }
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
pub struct RenderOutput { pub struct RenderOutput {
pub data: RenderOutputType, pub data: RenderOutputType,
@@ -540,25 +545,13 @@ mod fake_hash {
macro_rules! thumbnail_render { macro_rules! thumbnail_render {
( $( $ty:ty ),* $(,)? ) => { ( $( $ty:ty ),* $(,)? ) => {
pub fn render_thumbnail_if_change(new_value: &Arc<dyn std::any::Any + Send + Sync>, old_value: Option<&Arc<dyn std::any::Any + Send + Sync>>) -> ThumbnailRenderResult { pub fn render_thumbnail(new_value: &Arc<dyn std::any::Any + Send + Sync>) -> Option<String> {
$( $(
if let Some(new_value) = new_value.downcast_ref::<$ty>() { if let Some(new_value) = new_value.downcast_ref::<$ty>() {
match old_value { return Some(new_value.render_thumbnail());
None => return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail()),
Some(old_value) => {
if let Some(old_value) = old_value.downcast_ref::<$ty>() {
match new_value == old_value {
true => return ThumbnailRenderResult::NoChange,
false => return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail())
}
} else {
return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail())
}
},
}
} }
)* )*
return ThumbnailRenderResult::ClearThumbnail; return None;
} }
}; };
} }
+4 -7
View File
@@ -96,7 +96,7 @@ impl ProtoNetwork {
pub struct UpstreamInputMetadata { pub struct UpstreamInputMetadata {
pub input_sni: SNI, pub input_sni: SNI,
// Context dependencies are accumulated during compilation, then replaced with the difference between the node's dependencies and the inputs dependencies // Context dependencies are accumulated during compilation, then replaced with the difference between the node's dependencies and the inputs dependencies
pub context_dependencies: ContextDependencies, pub nullify: ContextDependencies,
// If the upstream node is a value node, then do not nullify since the value nodes do not have a cache inserted after them // If the upstream node is a value node, then do not nullify since the value nodes do not have a cache inserted after them
pub is_value: bool, pub is_value: bool,
} }
@@ -112,6 +112,7 @@ pub struct NodeConstructionArgs {
pub inputs: Vec<Option<UpstreamInputMetadata>>, pub inputs: Vec<Option<UpstreamInputMetadata>>,
// The union of all input context dependencies and the nodes context dependency. Used to generate the context nullification for the editor entry point // The union of all input context dependencies and the nodes context dependency. Used to generate the context nullification for the editor entry point
pub context_dependencies: ContextDependencies, pub context_dependencies: ContextDependencies,
pub cache_output: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -258,7 +259,7 @@ impl Debug for GraphErrorType {
} }
} }
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
pub struct GraphError { pub struct GraphError {
pub original_location: OriginalLocation, pub original_location: OriginalLocation,
pub identifier: Cow<'static, str>, pub identifier: Cow<'static, str>,
@@ -279,11 +280,7 @@ impl GraphError {
} }
} }
} }
impl Debug for GraphError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeGraphError").field("identifier", &self.identifier.to_string()).field("error", &self.error).finish()
}
}
pub type GraphErrors = Vec<GraphError>; pub type GraphErrors = Vec<GraphError>;
/// The `TypingContext` is used to store the types of the nodes indexed by their stable node id. /// The `TypingContext` is used to store the types of the nodes indexed by their stable node id.
+1 -1
View File
@@ -185,7 +185,7 @@ fn compile_graph(document_string: String, application_io: Arc<WasmApplicationIo>
let mut wrapped_network = wrap_network_in_scope(network, Arc::new(FontCache::default()), EditorMetadata::default(), application_io); let mut wrapped_network = wrap_network_in_scope(network, Arc::new(FontCache::default()), EditorMetadata::default(), application_io);
wrapped_network.flatten().map(|result| result.0).map_err(|x| x.into()) wrapped_network.compile().map(|result| result.0).map_err(|x| x.into())
} }
fn create_executor(proto_network: ProtoNetwork) -> Result<DynamicExecutor, Box<dyn Error>> { fn create_executor(proto_network: ProtoNetwork) -> Result<DynamicExecutor, Box<dyn Error>> {
+34 -9
View File
@@ -2,6 +2,7 @@ use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWar
use dyn_any::DynAny; use dyn_any::DynAny;
use fastnoise_lite; use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2}; use glam::{DAffine2, DVec2, Vec2};
use graphene_core::ExtractDownstreamTransform;
use graphene_core::blending::AlphaBlending; use graphene_core::blending::AlphaBlending;
use graphene_core::color::Color; use graphene_core::color::Color;
use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut}; use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
@@ -30,7 +31,7 @@ impl From<std::io::Error> for Error {
} }
#[node_macro::node(category("Debug: Raster"))] #[node_macro::node(category("Debug: Raster"))]
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable<CPU>) -> RasterDataTable<CPU> { pub fn sample_image(ctx: impl ExtractFootprint + ExtractDownstreamTransform + Clone + Send, image_frame: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
let mut result_table = RasterDataTable::default(); let mut result_table = RasterDataTable::default();
for mut image_frame_instance in image_frame.instance_iter() { for mut image_frame_instance in image_frame.instance_iter() {
@@ -40,8 +41,17 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Rast
// Resize the image using the image crate // Resize the image using the image crate
let data = bytemuck::cast_vec(image.data.clone()); let data = bytemuck::cast_vec(image.data.clone());
let footprint = ctx.footprint(); // TODO: Feed as input for error handling
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let Some(footprint) = ctx.try_footprint().cloned() else {
continue;
};
let Some(downstream_transform) = ctx.try_downstream_transform() else {
continue;
};
let viewport_bounds = footprint.viewport_bounds_in_local_space(downstream_transform);
let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox(); let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox();
let intersection = viewport_bounds.intersect(&image_bounds); let intersection = viewport_bounds.intersect(&image_bounds);
let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)); let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64));
@@ -313,7 +323,7 @@ pub fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable<CPU>) -> Ra
#[node_macro::node(category("Raster: Pattern"))] #[node_macro::node(category("Raster: Pattern"))]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn noise_pattern( pub fn noise_pattern(
ctx: impl ExtractFootprint + Ctx, ctx: impl ExtractFootprint + ExtractDownstreamTransform + Ctx,
_primary: (), _primary: (),
clip: bool, clip: bool,
seed: u32, seed: u32,
@@ -331,8 +341,15 @@ pub fn noise_pattern(
cellular_return_type: CellularReturnType, cellular_return_type: CellularReturnType,
cellular_jitter: f64, cellular_jitter: f64,
) -> RasterDataTable<CPU> { ) -> RasterDataTable<CPU> {
let footprint = ctx.footprint(); // TODO: Feed as input for error handling
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let Some(footprint) = ctx.try_footprint().copied() else {
return RasterDataTable::default();
};
let Some(downstream_transform) = ctx.try_downstream_transform() else {
return RasterDataTable::default();
};
let viewport_bounds = footprint.viewport_bounds_in_local_space(downstream_transform);
let mut size = viewport_bounds.size(); let mut size = viewport_bounds.size();
let mut offset = viewport_bounds.start; let mut offset = viewport_bounds.start;
@@ -468,9 +485,17 @@ pub fn noise_pattern(
} }
#[node_macro::node(category("Raster: Pattern"))] #[node_macro::node(category("Raster: Pattern"))]
pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> { pub fn mandelbrot(ctx: impl ExtractFootprint + ExtractDownstreamTransform + Send) -> RasterDataTable<CPU> {
let footprint = ctx.footprint(); // TODO: Feed as input for error handling
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let Some(footprint) = ctx.try_footprint().cloned() else {
return RasterDataTable::default();
};
let Some(downstream_transform) = ctx.try_downstream_transform() else {
return RasterDataTable::default();
};
let viewport_bounds = footprint.viewport_bounds_in_local_space(downstream_transform);
let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox(); let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox();
let intersection = viewport_bounds.intersect(&image_bounds); let intersection = viewport_bounds.intersect(&image_bounds);
+42 -59
View File
@@ -1,15 +1,16 @@
use dyn_any::StaticType; use dyn_any::StaticType;
use glam::DAffine2;
pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode}; pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer}; use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer};
use graphene_core::Context; use graphene_core::Context;
use graphene_core::ContextDependencies; use graphene_core::ContextDependencies;
use graphene_core::EditorContext;
use graphene_core::NodeIO; use graphene_core::NodeIO;
use graphene_core::OwnedContextImpl; use graphene_core::OwnedContextImpl;
use graphene_core::WasmNotSend; use graphene_core::WasmNotSend;
pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode}; pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};
use graphene_core::transform::Footprint; use graphene_core::uuid::SNI;
pub use graphene_core::{Node, generic, ops}; pub use graphene_core::{Node, generic, ops};
use std::sync::mpsc::Sender;
pub trait IntoTypeErasedNode<'n> { pub trait IntoTypeErasedNode<'n> {
fn into_type_erased(self) -> TypeErasedBox<'n>; fn into_type_erased(self) -> TypeErasedBox<'n>;
@@ -72,63 +73,6 @@ pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> Do
// } // }
// } // }
#[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>,
// #[serde(skip)]
// pub editor_var_args: Option<(Vec<String>, Vec<Arc<Box<[dyn std::any::Any + 'static + std::panic::UnwindSafe]>>>)>,
}
unsafe impl StaticType for EditorContext {
type Static = EditorContext;
}
// impl Default for EditorContext {
// fn default() -> Self {
// EditorContext {
// footprint: None,
// downstream_transform: None,
// real_time: None,
// animation_time: None,
// index: None,
// // editor_var_args: None,
// }
// }
// }
impl EditorContext {
pub fn to_owned_context(&self) -> OwnedContextImpl {
let mut context = OwnedContextImpl::default();
if let Some(footprint) = self.footprint {
context.set_footprint(footprint);
}
if let Some(footprint) = self.footprint {
context.set_footprint(footprint);
}
// if let Some(downstream_transform) = self.downstream_transform {
// context.set_downstream_transform(downstream_transform);
// }
if let Some(real_time) = self.real_time {
context.set_real_time(real_time);
}
if let Some(animation_time) = self.animation_time {
context.set_animation_time(animation_time);
}
if let Some(index) = self.index {
context.set_index(index);
}
context
// if let Some(editor_var_args) = self.editor_var_args {
// let (variable_names, values)
// context.set_varargs((variable_names, values))
// }
}
}
pub struct NullificationNode { pub struct NullificationNode {
first: SharedNodeContainer, first: SharedNodeContainer,
nullify: ContextDependencies, nullify: ContextDependencies,
@@ -142,7 +86,9 @@ impl<'i> Node<'i, Any<'i>> for NullificationNode {
Ok(context) => match *context { Ok(context) => match *context {
Some(context) => { Some(context) => {
let mut new_context: OwnedContextImpl = OwnedContextImpl::from(context); let mut new_context: OwnedContextImpl = OwnedContextImpl::from(context);
// log::debug!("Nullifying context: {:?} fields: {:?}", new_context, self.nullify);
new_context.nullify(&self.nullify); new_context.nullify(&self.nullify);
// log::debug!("Evaluating input with: {:?}", new_context);
Box::new(new_context.into_context()) as Any<'i> Box::new(new_context.into_context()) as Any<'i>
} }
None => { None => {
@@ -162,3 +108,40 @@ impl NullificationNode {
Self { first, nullify } Self { first, nullify }
} }
} }
pub struct ContextMonitorNode {
sni: SNI,
input_index: usize,
first: SharedNodeContainer,
sender: Sender<(SNI, usize, EditorContext)>,
}
impl<'i> Node<'i, Any<'i>> for ContextMonitorNode {
type Output = DynFuture<'i, Any<'i>>;
fn eval(&'i self, input: Any<'i>) -> Self::Output {
Box::pin(async move {
let new_input = match dyn_any::try_downcast::<Context>(input) {
Ok(context) => match *context {
Some(context) => {
let editor_context = context.to_editor_context();
let _ = self.sender.clone().send((self.sni, self.input_index, editor_context));
Box::new(Some(context)) as Any<'i>
}
None => {
let none: Context = None;
// self.sender.clone().send((self.sni, self.input_index, editor_context));
Box::new(none) as Any<'i>
}
},
Err(other_input) => other_input,
};
self.first.eval(new_input).await
})
}
}
impl ContextMonitorNode {
pub fn new(sni: SNI, input_index: usize, first: SharedNodeContainer, sender: Sender<(SNI, usize, EditorContext)>) -> ContextMonitorNode {
ContextMonitorNode { sni, input_index, first, sender }
}
}
+15 -6
View File
@@ -212,11 +212,13 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams); fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams);
fn render_thumbnail(&self) -> String { fn render_thumbnail(&self) -> String {
let bounds = self.bounding_box(DAffine2::IDENTITY, true); let Some(bounds) = self.bounding_box(DAffine2::IDENTITY, true) else {
return String::new();
};
let render_params = RenderParams { let render_params = RenderParams {
view_mode: ViewMode::Normal, view_mode: ViewMode::Normal,
culling_bounds: bounds, culling_bounds: Some(bounds),
thumbnail: true, thumbnail: true,
hide_artboards: false, hide_artboards: false,
for_export: false, for_export: false,
@@ -224,15 +226,22 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
alignment_parent_transform: None, alignment_parent_transform: None,
}; };
// Render the thumbnail data into an SVG string
let mut render = SvgRender::new(); let mut render = SvgRender::new();
self.render_svg(&mut render, &render_params); self.render_svg(&mut render, &render_params);
// let center = (bounds[0] + bounds[1]) / 2.;
// let size = bounds[1] - bounds[0];
// let scale_x = 32.0 / size.x;
// let scale_y = 24.0 / size.y;
// let scale = scale_x.min(scale_y);
// render.wrap_with_transform()
// Give the SVG a viewbox and outer <svg>...</svg> wrapper tag // Give the SVG a viewbox and outer <svg>...</svg> wrapper tag
// let [min, max] = bounds.unwrap_or_default(); render.format_svg(bounds[0], bounds[1]);
// render.format_svg(min, max);
render.svg.to_svg_string() render.svg.to_svg_string()
// format!(r#"<g transform="scale({}) translate({}, {})">{}</g>"#, scale, -center.x, -center.y, render.svg.to_svg_string())
} }
/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection. /// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.
@@ -7,7 +7,7 @@ use interpreted_executor::dynamic_executor::DynamicExecutor;
pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) { pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {
let mut network = load_from_name(name); let mut network = load_from_name(name);
let proto_network = network.flatten().unwrap().0; let proto_network = network.compile().unwrap().0;
let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap(); let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap();
(executor, proto_network) (executor, proto_network)
} }
@@ -7,7 +7,7 @@ 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 mut network = load_from_name(name); let mut network = load_from_name(name);
let proto_network = network.flatten().unwrap().0; let proto_network = network.compile().unwrap().0;
let empty = ProtoNetwork::default(); let empty = ProtoNetwork::default();
let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap();
@@ -15,7 +15,7 @@ fn update_executor<M: Measurement>(name: &str, c: &mut BenchmarkGroup<M>) {
c.bench_function(name, |b| { c.bench_function(name, |b| {
b.iter_batched( b.iter_batched(
|| (executor.clone(), proto_network.clone()), || (executor.clone(), proto_network.clone()),
|(mut executor, network)| futures::executor::block_on(executor.update(black_box(network))), |(mut executor, network)| futures::executor::block_on(executor.update(black_box(network, None))),
criterion::BatchSize::SmallInput, criterion::BatchSize::SmallInput,
) )
}); });
@@ -30,7 +30,7 @@ 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 mut network = load_from_name(name); let mut network = load_from_name(name);
let proto_network = network.flatten().unwrap().0; let proto_network = network.compile().unwrap().0;
let executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap(); let executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap();
let context = graphene_std::any::EditorContext::default(); let context = graphene_std::any::EditorContext::default();
@@ -16,7 +16,7 @@ fn update_executor(c: &mut Criterion) {
let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap();
(executor, proto_network) (executor, proto_network)
}, },
|(mut executor, network)| futures::executor::block_on(executor.update(criterion::black_box(network))), |(mut executor, network)| futures::executor::block_on(executor.update(criterion::black_box(network, None))),
criterion::BatchSize::SmallInput, criterion::BatchSize::SmallInput,
) )
}); });
@@ -4,12 +4,13 @@ use graph_craft::document::value::{TaggedValue, UpcastNode};
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, UpstreamInputMetadata}; use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, UpstreamInputMetadata};
use graph_craft::proto::{GraphErrorType, GraphErrors}; use graph_craft::proto::{GraphErrorType, GraphErrors};
use graph_craft::{Type, concrete}; use graph_craft::{Type, concrete};
use graphene_std::any::{EditorContext, NullificationNode}; use graphene_std::any::{ContextMonitorNode, NullificationNode};
use graphene_std::memo::{MonitorIntrospectResult, MonitorMemoNodeState};
use graphene_std::uuid::{NodeId, SNI}; use graphene_std::uuid::{NodeId, SNI};
use graphene_std::{Context, ContextDependencies, NodeIOTypes}; use graphene_std::{Context, ContextDependencies, EditorContext, NodeIOTypes};
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::mpsc::Sender;
/// 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)]
@@ -21,7 +22,7 @@ pub struct DynamicExecutor {
typing_context: TypingContext, typing_context: TypingContext,
// TODO: Add lifetime for removed nodes so that if a SNI changes, then changes back to its previous SNI, the node does // TODO: Add lifetime for removed nodes so that if a SNI changes, then changes back to its previous SNI, the node does
// not have to be reinserted // not have to be reinserted
// lifetime: HashSet<(SNI, usize)>, // lifetime: HashSet<(Vec<NodeId>, usize)>,
} }
impl Default for DynamicExecutor { impl Default for DynamicExecutor {
@@ -45,13 +46,14 @@ impl DynamicExecutor {
/// 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<(Vec<(SNI, NodeIOTypes)>, Vec<SNI>), GraphErrors> { pub async fn update(&mut self, proto_network: ProtoNetwork, context_sender: Option<&Sender<(SNI, usize, EditorContext)>>) -> Result<(Vec<(SNI, NodeIOTypes)>, Vec<SNI>), GraphErrors> {
self.output = Some(proto_network.output); self.output = Some(proto_network.output);
self.typing_context.update(&proto_network)?; self.typing_context.update(&proto_network)?;
let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?; let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context, context_sender).await?;
let mut remove = Vec::new(); let mut remove = Vec::new();
for sni in orphaned_proto_nodes { for sni in orphaned_proto_nodes {
remove.push(sni); remove.push(sni);
self.tree.free_node(&sni);
self.typing_context.remove_inference(&sni); self.typing_context.remove_inference(&sni);
} }
@@ -70,9 +72,18 @@ impl DynamicExecutor {
} }
// Introspect the cached output of any protonode // Introspect the cached output of any protonode
pub fn introspect(&self, protonode: SNI, check_if_evaluated: bool) -> Result<Option<Arc<dyn std::any::Any + Send + Sync>>, IntrospectError> { pub fn introspect(&self, protonode: SNI) -> Result<MonitorIntrospectResult, IntrospectError> {
let inserted_node = self.tree.nodes.get(&protonode).ok_or(IntrospectError::ProtoNodeNotFound(protonode))?; let inserted_node = self.tree.nodes.get(&protonode).ok_or(IntrospectError::ProtoNodeNotFound(protonode))?;
Ok(inserted_node.cached_protonode.introspect(check_if_evaluated)) Ok(inserted_node.cached_protonode.introspect())
}
// If the cache is disabled, then it sets the state to save the first evaluation. If its enabled, then it does nothing
pub fn cache_first_evaluation(&self, protonode: &SNI) {
let Some(inserted_node) = self.tree.nodes.get(protonode) else {
log::error!("Could not get inserted protonode when setting cache_first_evaluation {:?}", protonode);
return;
};
inserted_node.cached_protonode.cache_first_evaluation();
} }
pub fn input_type(&self) -> Option<Type> { pub fn input_type(&self) -> Option<Type> {
@@ -188,7 +199,6 @@ struct InsertedProtonode {
/// 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 to dynamically typed proto nodes, as well as the auto inserted MonitorCache nodes, and editor entry point
nodes: HashMap<SNI, InsertedProtonode>, nodes: HashMap<SNI, InsertedProtonode>,
} }
@@ -196,13 +206,18 @@ impl BorrowTree {
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> { pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
let mut nodes = BorrowTree::default(); let mut nodes = BorrowTree::default();
for node in proto_network.into_nodes() { for node in proto_network.into_nodes() {
nodes.push_node(node, typing_context).await? nodes.push_node(node, typing_context, None).await?
} }
Ok(nodes) Ok(nodes)
} }
/// Pushes new nodes into the tree and returns a vec of document nodes that had their types changed, and a vec of all nodes that were removed (including auto inserted value nodes) /// 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<SNI>, HashSet<SNI>), GraphErrors> { pub async fn update(
&mut self,
proto_network: ProtoNetwork,
typing_context: &TypingContext,
context_sender: Option<&Sender<(SNI, usize, EditorContext)>>,
) -> Result<(Vec<SNI>, HashSet<SNI>), GraphErrors> {
let mut old_nodes = self.nodes.keys().copied().into_iter().collect::<HashSet<_>>(); let mut old_nodes = self.nodes.keys().copied().into_iter().collect::<HashSet<_>>();
// List of all document node paths that need to be updated, which occurs if their path changes or type changes // List of all document node paths that need to be updated, which occurs if their path changes or type changes
let mut nodes_with_new_type = Vec::new(); let mut nodes_with_new_type = Vec::new();
@@ -211,7 +226,7 @@ impl BorrowTree {
old_nodes.remove(&sni); old_nodes.remove(&sni);
if !self.nodes.contains_key(&sni) { if !self.nodes.contains_key(&sni) {
nodes_with_new_type.push(sni); nodes_with_new_type.push(sni);
self.push_node(node, typing_context).await?; self.push_node(node, typing_context, context_sender.clone()).await?;
} }
} }
@@ -333,7 +348,7 @@ impl BorrowTree {
/// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments. /// - 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.
/// Thumbnails is a mapping of the protonode input to the rendered thumbnail through the monitor cache node /// Thumbnails is a mapping of the protonode input to the rendered thumbnail through the monitor cache node
async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> { async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext, context_sender: Option<&Sender<(SNI, usize, EditorContext)>>) -> Result<(), GraphErrors> {
let sni = proto_node.stable_node_id; let sni = proto_node.stable_node_id;
match proto_node.construction_args { match proto_node.construction_args {
ConstructionArgs::Value(value) => { ConstructionArgs::Value(value) => {
@@ -371,10 +386,23 @@ impl BorrowTree {
let protonode_inputs = construction_nodes let protonode_inputs = construction_nodes
.iter() .iter()
.zip(node_construction_args.inputs.into_iter()) .zip(node_construction_args.inputs.into_iter())
.map(|(inserted_protonode, input_metadata)| { .enumerate()
let previous_input = inserted_protonode.cached_protonode.clone(); .map(|(input_index, (upstream_inserted_protonode, input_metadata))| {
let input_context_dependencies = input_metadata.unwrap().context_dependencies; let mut previous_input = upstream_inserted_protonode.cached_protonode.clone();
// Insert context monitoring if enabled
if let Some(context_sender) = context_sender {
let context_monitor = ContextMonitorNode::new(sni, input_index, previous_input, context_sender.clone());
let node = Box::new(context_monitor) as TypeErasedBox<'_>;
previous_input = NodeContainer::new(node);
}
let input_context_dependencies = input_metadata.unwrap().nullify;
if !input_context_dependencies.is_empty() { if !input_context_dependencies.is_empty() {
// If nullifying the inputs such that the context is completely empty, then cache the upstream output
if upstream_inserted_protonode.nullify_when_calling == ContextDependencies::all_context_dependencies() {
upstream_inserted_protonode.cached_protonode.permanently_enable_cache();
}
let nullification_node = NullificationNode::new(previous_input, input_context_dependencies); let nullification_node = NullificationNode::new(previous_input, input_context_dependencies);
let node = Box::new(nullification_node) as TypeErasedBox<'_>; let node = Box::new(nullification_node) as TypeErasedBox<'_>;
NodeContainer::new(node) NodeContainer::new(node)
@@ -387,18 +415,20 @@ impl BorrowTree {
let node = constructor(protonode_inputs).await; let node = constructor(protonode_inputs).await;
let protonode = NodeContainer::new(node); let protonode = NodeContainer::new(node);
// Insert cache nodes on the output if possible // When evaluating the node from the editor, nullify all context fields it is not dependent on
let nullify_when_calling = node_construction_args.context_dependencies.inverse();
let cached_protonode = if let Some(cache_constructor) = typing_context.cache_constructor(&types.return_value.nested_type()) { let cached_protonode = if let Some(cache_constructor) = typing_context.cache_constructor(&types.return_value.nested_type()) {
let cache = cache_constructor(protonode); let cache = cache_constructor(protonode, MonitorMemoNodeState::Disabled);
let cache_node_container = NodeContainer::new(cache); let cache_node_container = NodeContainer::new(cache);
if node_construction_args.cache_output {
cache_node_container.permanently_enable_cache();
}
cache_node_container cache_node_container
} else { } else {
protonode protonode
}; };
// When evaluating the node from the editor, nullify all context fields it is not dependent on
let nullify_when_calling = node_construction_args.context_dependencies.inverse();
let inserted_protonode = InsertedProtonode { let inserted_protonode = InsertedProtonode {
cached_protonode, cached_protonode,
nullify_when_calling, nullify_when_calling,
@@ -411,27 +441,27 @@ impl BorrowTree {
} }
} }
#[cfg(test)] // #[cfg(test)]
mod test { // mod test {
use super::*; // use super::*;
use graph_craft::{document::value::TaggedValue, proto::NodeValueArgs}; // use graph_craft::{document::value::TaggedValue, proto::NodeValueArgs};
use graphene_std::uuid::NodeId; // 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( // let val_1_protonode = ProtoNode::value(
ConstructionArgs::Value(NodeValueArgs { // ConstructionArgs::Value(NodeValueArgs {
value: Some(TaggedValue::U32(2u32).into()), // value: Some(TaggedValue::U32(2u32).into()),
connector_paths: Vec::new(), // connector_paths: Vec::new(),
}), // }),
NodeId(0), // NodeId(0),
); // );
let context = TypingContext::default(); // let context = TypingContext::default();
let future = tree.push_node(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.nodes.get(&NodeId(0)).expect("Node should be added to tree"); // let _node = tree.nodes.get(&NodeId(0)).expect("Node should be added to tree");
let result = futures::executor::block_on(tree.eval_tagged_value(NodeId(0), ())); // let result = futures::executor::block_on(tree.eval_tagged_value(NodeId(0), ()));
assert_eq!(result, Some(TaggedValue::U32(2u32).into())); // assert_eq!(result, Some(TaggedValue::U32(2u32).into()));
} // }
} // }
+1 -1
View File
@@ -42,7 +42,7 @@ mod tests {
use crate::dynamic_executor::DynamicExecutor; use crate::dynamic_executor::DynamicExecutor;
let protonetwork = network.flatten().map(|result| result.0).expect("Graph should be generated"); let protonetwork = network.compile().map(|result| result.0).expect("Graph should be generated");
let _exec = block_on(DynamicExecutor::new(protonetwork)).map(|_e| panic!("The network should not type check ")).unwrap_err(); let _exec = block_on(DynamicExecutor::new(protonetwork)).map(|_e| panic!("The network should not type check ")).unwrap_err();
} }
@@ -332,8 +332,8 @@ mod node_registry_macros {
macro_rules! cache_node { macro_rules! cache_node {
($type:ty) => { ($type:ty) => {
(concrete!($type), |arg| { (concrete!($type), |arg, state| {
let node = <graphene_core::memo::MonitorMemoNode<_, _>>::new(graphene_std::registry::downcast_node::<graphene_std::Context, $type>(arg)); let node = <graphene_core::memo::MonitorMemoNode<_, _>>::new(graphene_std::registry::downcast_node::<graphene_std::Context, $type>(arg), state);
let any: DynAnyNode<_, _, _> = graphene_std::any::DynAnyNode::new(node); let any: DynAnyNode<_, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox Box::new(any) as TypeErasedBox
}) })
+8
View File
@@ -643,6 +643,14 @@ impl ParsedNodeFn {
"ExtractAnimationTime" => dependency_tokens.push(quote::quote! {ExtractAnimationTime}), "ExtractAnimationTime" => dependency_tokens.push(quote::quote! {ExtractAnimationTime}),
"ExtractIndex" => dependency_tokens.push(quote::quote! {ExtractIndex}), "ExtractIndex" => dependency_tokens.push(quote::quote! {ExtractIndex}),
"ExtractVarArgs" => dependency_tokens.push(quote::quote! {ExtractVarArgs}), "ExtractVarArgs" => dependency_tokens.push(quote::quote! {ExtractVarArgs}),
"ExtractAll" => {
dependency_tokens.push(quote::quote! {ExtractFootprint});
dependency_tokens.push(quote::quote! {ExtractDownstreamTransform});
dependency_tokens.push(quote::quote! {ExtractRealTime});
dependency_tokens.push(quote::quote! {ExtractAnimationTime});
dependency_tokens.push(quote::quote! {ExtractIndex});
dependency_tokens.push(quote::quote! {ExtractVarArgs});
}
_ => {} _ => {}
} }
} }